Task 144: .DNG File Format
Task 144: .DNG File Format
File Format Specifications for the .DNG File Format
The .DNG (Digital Negative) file format is an open, lossless raw image format developed by Adobe for digital photography. It is based on the TIFF 6.0 specification and is fully compatible with the TIFF/EP standard. The format structures image data and metadata using a hierarchical system of Image File Directories (IFDs), where the primary IFD (typically IFD 0) contains core metadata and references to sub-IFDs for raw image data, previews, thumbnails, and additional elements such as depth maps or semantic masks. Key structural features include support for big-endian or little-endian byte order, opcode lists for extensible processing, floating-point image data, and extensions for large files via BigTIFF. The format ensures backward compatibility through versioning tags and is designed to address proprietary raw format limitations by providing a standardized, archival solution.
List of Properties Intrinsic to the .DNG File Format
The properties listed below represent the core structural elements and metadata tags specific to the .DNG format, derived from its TIFF-based architecture. These include required and optional tags that define file versioning, camera calibration, image processing, and data organization. Tags are identified by their decimal and hexadecimal IDs, type, count, requirement status, and description. This list focuses on DNG-specific extensions (tag IDs starting from 50706), excluding general TIFF tags unless uniquely modified for DNG.
Tag Name | Tag ID (Decimal / Hex) | Type | Count | Required / Optional | Description |
---|---|---|---|---|---|
DNGVersion | 50706 / C612 | BYTE | 4 | Required | Specifies the DNG version number (e.g., 1, 6, 0, 0 for version 1.6.0.0). |
DNGBackwardVersion | 50707 / C613 | BYTE | 4 | Required | Indicates the oldest DNG version compatible with the file. |
UniqueCameraModel | 50708 / C614 | ASCII | Variable (null-terminated) | Required | Unique, non-localized string identifying the camera model. |
LocalizedCameraModel | 50709 / C615 | BYTE / ASCII | Variable | Optional | Localized version of the camera model string. |
CFAPlaneColor | 50710 / C616 | BYTE | SamplesPerPixel | Required (for mosaiced data) | Maps CFA values to color planes. |
CFALayout | 50711 / C617 | SHORT | 1 | Optional | Describes the spatial layout of CFA pattern (e.g., rectangular or staggered). |
LinearizationTable | 50712 / C618 | SHORT | Variable | Optional | Lookup table for linearizing raw data. |
BlackLevelRepeatDim | 50713 / C619 | SHORT | 2 | Required | Dimensions of repeating black level pattern. |
BlackLevel | 50714 / C61A | RATIONAL | BlackLevelRepeatDim product | Required | Zero light encoding level for each color plane. |
BlackLevelDeltaH | 50715 / C61B | SRATIONAL | SamplesPerRow | Optional | Per-row zero light encoding offsets. |
BlackLevelDeltaV | 50716 / C61C | SRATIONAL | ImageLength | Optional | Per-column zero light encoding offsets. |
WhiteLevel | 50717 / C61D | LONG | SamplesPerPixel | Required | Full sensor encoding level for each color plane. |
DefaultScale | 50718 / C61E | RATIONAL | 2 | Required | Nominal pixel scaling factors (horizontal and vertical). |
DefaultCropOrigin | 50719 / C61F | LONG / SHORT | 2 | Required | Origin of default user crop rectangle in raw coordinates. |
DefaultCropSize | 50720 / C620 | LONG / SHORT | 2 | Required | Size of default user crop rectangle in raw coordinates. |
ColorMatrix1 | 50721 / C621 | SRATIONAL | 3 × ColorPlanes | Required | Matrix mapping camera colors to XYZ with reference illuminant 1. |
ColorMatrix2 | 50722 / C622 | SRATIONAL | 3 × ColorPlanes | Optional | Matrix mapping camera colors to XYZ with reference illuminant 2. |
CameraCalibration1 | 50723 / C623 | SRATIONAL | ColorPlanes² | Optional | Calibration matrix for non-DNG raw converters (illuminant 1). |
CameraCalibration2 | 50724 / C624 | SRATIONAL | ColorPlanes² | Optional | Calibration matrix for non-DNG raw converters (illuminant 2). |
ReductionMatrix1 | 50725 / C625 | SRATIONAL | 3 × ColorPlanes | Optional | Dimensionality reduction matrix for >3 color planes (illuminant 1). |
ReductionMatrix2 | 50726 / C626 | SRATIONAL | 3 × ColorPlanes | Optional | Dimensionality reduction matrix for >3 color planes (illuminant 2). |
AnalogBalance | 50727 / C627 | RATIONAL | ColorPlanes | Optional | Gain applied to each color plane before linearization. |
AsShotNeutral | 50728 / C628 | SHORT / RATIONAL | ColorPlanes | Optional | Selected white balance in linear reference space. |
AsShotWhiteXY | 50729 / C629 | RATIONAL | 2 | Optional | Selected white balance in xy chromaticity coordinates. |
BaselineExposure | 50730 / C62A | SRATIONAL | 1 | Required | Relative exposure value offset for rendered images. |
BaselineNoise | 50731 / C62B | RATIONAL | 1 | Required | Relative noise level at ISO 100. |
BaselineSharpness | 50732 / C62C | RATIONAL | 1 | Required | Relative sharpness at ISO 100. |
BayerGreenSplit | 50733 / C62D | LONG | 1 | Optional | Difference in green channel sampling for Bayer CFA. |
LinearResponseLimit | 50734 / C62E | RATIONAL | 1 | Required | Fraction of raw data that is non-linear. |
CameraSerialNumber | 50735 / C62F | ASCII | Variable | Optional | Serial number of the camera. |
LensInfo | 50736 / C630 | RATIONAL | 4 | Optional | Lens specifications (min/max focal length and f-number). |
ChromaBlurRadius | 50737 / C631 | RATIONAL | 1 | Optional | Radius for chroma blur filter. |
AntiAliasStrength | 50738 / C632 | RATIONAL | 1 | Optional | Relative strength of anti-alias filter. |
ShadowScale | 50739 / C633 | RATIONAL | 1 | Required | Scale factor for shadows in linear processing. |
DNGPrivateData | 50740 / C634 | BYTE | Variable | Optional | Private data storage for makers. |
MakerNoteSafety | 50741 / C635 | SHORT | 1 | Optional | Indicates if MakerNote can be safely rewritten. |
CalibrationIlluminant1 | 50778 / C65A | SHORT | 1 | Required (if ColorMatrix1 present) | Reference illuminant for ColorMatrix1. |
CalibrationIlluminant2 | 50779 / C65B | SHORT | 1 | Optional | Reference illuminant for ColorMatrix2. |
BestQualityScale | 50780 / C65C | RATIONAL | 1 | Optional | Scale factor for best quality rendering. |
RawDataUniqueID | 50781 / C65D | BYTE | 16 | Optional | Unique identifier for raw image data. |
OriginalRawFileName | 50827 / C68B | BYTE / ASCII | Variable | Optional | Name of embedded or external original raw file. |
OriginalRawFileData | 50828 / C68C | UNDEF | Variable | Optional | Data of the original raw file. |
ActiveArea | 50829 / C68D | LONG / SHORT | 4 | Optional | Active (non-masked) area of the sensor. |
MaskedAreas | 50830 / C68E | LONG / SHORT | 4 × Rectangles | Optional | List of masked pixel rectangles. |
AsShotICCProfile | 50831 / C68F | UNDEF | Variable | Optional | ICC profile for as-shot color space. |
AsShotPreProfileMatrix | 50832 / C690 | SRATIONAL | 3 × ColorPlanes or ColorPlanes | Optional | Matrix for as-shot ICC profile. |
CurrentICCProfile | 50833 / C691 | UNDEF | Variable | Optional | Current ICC profile. |
CurrentPreProfileMatrix | 50834 / C692 | SRATIONAL | 3 × ColorPlanes or ColorPlanes | Optional | Matrix for current ICC profile. |
ColorimetricReference | 50879 / C6BF | SHORT | 1 | Optional | Colorimetric reference (e.g., scene or output). |
CameraCalibrationSignature | 50931 / C6F3 | BYTE / ASCII | Variable | Optional | Signature for CameraCalibration matrices. |
ProfileCalibrationSignature | 50932 / C6F4 | BYTE / ASCII | Variable | Optional | Signature for color matrix tags. |
ExtraCameraProfiles | 50933 / C6F5 | LONG | Variable | Optional | Offsets to additional camera profile IFDs. |
AsShotProfileName | 50934 / C6F6 | BYTE / ASCII | Variable | Optional | Name of as-shot profile. |
NoiseReductionApplied | 50935 / C6F7 | RATIONAL | 1 | Optional | Fraction of noise reduction applied. |
ProfileName | 50936 / C6F8 | BYTE / ASCII | Variable | Optional | UTF-8 name of the camera profile. |
ProfileHueSatMapDims | 50937 / C6F9 | LONG | 3 | Optional | Dimensions for hue/saturation/value map. |
ProfileHueSatMapData1 | 50938 / C6FA | FLOAT | Hue × Sat × Val × 3 | Optional | Hue/saturation/value adjustment map for illuminant 1. |
ProfileHueSatMapData2 | 50939 / C6FB | FLOAT | Hue × Sat × Val × 3 | Optional | Hue/saturation/value adjustment map for illuminant 2. |
ProfileToneCurve | 50940 / C6FC | FLOAT | Variable (even) | Optional | Tone curve points for the profile. |
ProfileEmbedPolicy | 50941 / C6FD | LONG | 1 | Optional | Policy for embedding the profile (e.g., allow copying). |
ProfileCopyright | 50942 / C6FE | BYTE / ASCII | Variable | Optional | Copyright string for the profile. |
ForwardMatrix1 | 50964 / C714 | SRATIONAL | 3 × ColorPlanes | Optional | Matrix mapping camera colors to XYZ D50 (illuminant 1). |
ForwardMatrix2 | 50965 / C715 | SRATIONAL | 3 × ColorPlanes | Optional | Matrix mapping camera colors to XYZ D50 (illuminant 2). |
PreviewApplicationName | 50966 / C716 | BYTE / ASCII | Variable | Optional | Name of application that created the preview. |
PreviewApplicationVersion | 50967 / C717 | BYTE / ASCII | Variable | Optional | Version of application that created the preview. |
PreviewSettingsName | 50968 / C718 | BYTE / ASCII | Variable | Optional | Name of settings used for the preview. |
PreviewSettingsDigest | 50969 / C719 | BYTE | 16 | Optional | Digest of settings used for the preview. |
PreviewColorSpace | 50970 / C71A | LONG | 1 | Optional | Color space of the preview (e.g., sRGB). |
PreviewDateTime | 50971 / C71B | ASCII | Variable | Optional | Date/time the preview was created (ISO 8601). |
RawImageDigest | 50972 / C71C | BYTE | 16 | Optional | MD5 digest of raw image data. |
OriginalRawFileDigest | 50973 / C71D | BYTE | 16 | Optional | MD5 digest of original raw file data. |
SubTileBlockSize | 50974 / C71E | LONG | 2 | Optional | Block dimensions for subtiled images. |
RowInterleaveFactor | 50975 / C71F | LONG | 1 | Optional | Number of interleaved rows. |
ProfileLookTableDims | 50981 / C725 | LONG | 3 | Optional | Dimensions for look table. |
ProfileLookTableData | 50982 / C726 | FLOAT | Hue × Sat × Val × 3 | Optional | Look table adjustment data. |
OpcodeList1 | 51008 / C740 | UNDEF | Variable | Optional | List of opcodes applied before demosaic. |
OpcodeList2 | 51009 / C741 | UNDEF | Variable | Optional | List of opcodes applied after demosaic, before output space. |
OpcodeList3 | 51022 / C74E | UNDEF | Variable | Optional | List of opcodes applied after output space mapping. |
NoiseProfile | 51041 / C761 | DOUBLE | 2 × ColorPlanes | Optional | Noise standard deviation for each plane. |
OriginalDefaultFinalSize | 51089 / C791 | LONG | 2 | Optional (for proxies) | Original file's default final size. |
OriginalBestQualityFinalSize | 51090 / C792 | LONG | 2 | Optional | Original file's best quality final size. |
OriginalDefaultCropSize | 51091 / C793 | RATIONAL / LONG | 2 | Optional | Original file's default crop size. |
ProfileHueSatMapEncoding | 51107 / C7A3 | LONG | 1 | Optional | Encoding for hue/sat maps (e.g., linear). |
ProfileLookTableEncoding | 51108 / C7A4 | LONG | 1 | Optional | Encoding for look table. |
BaselineExposureOffset | 51109 / C7A5 | SRATIONAL | 1 | Optional | Offset to baseline exposure. |
DefaultBlackRender | 51110 / C7A6 | LONG | 1 | Optional | Default black rendering hint. |
NewRawImageDigest | 51111 / C7A7 | BYTE | 16 | Optional | SHA-256 digest of raw image data. |
RawToPreviewGain | 51112 / C7A8 | DOUBLE | 1 | Optional | Gain applied from raw to preview. |
DefaultUserCrop | 51125 / C7B5 | RATIONAL | 4 | Optional | Default user crop rectangle. |
DepthFormat | 51177 / C7D9 | SHORT | 1 | Optional (for depth maps) | Format of depth data. |
DepthNear | 51178 / C7DA | RATIONAL | 1 | Optional | Near depth value. |
DepthFar | 51179 / C7DB | RATIONAL | 1 | Optional | Far depth value. |
DepthUnits | 51180 / C7DC | SHORT | 1 | Optional | Units for depth measurements. |
DepthMeasureType | 51181 / C7DD | SHORT | 1 | Optional | Type of depth measurement. |
EnhanceParams | 51182 / C7DE | ASCII | Variable | Optional | Parameters for image enhancement. |
This list encompasses the primary DNG-specific tags as defined in the specification up to version 1.6.0.0. Additional tags may exist for proprietary extensions or later versions, but these are intrinsic to the core format structure.
Two Direct Download Links for .DNG Files
- https://www.kenrockwell.com/trips/2009-10/images/L1004220.DNG
- https://www.kenrockwell.com/trips/2009-10/images/L1004235.DNG
Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .DNG File Analysis
The following is a self-contained HTML snippet with embedded JavaScript suitable for embedding in a Ghost blog post. It creates a drag-and-drop area where users can upload a .DNG file. The script reads the file as binary data, parses the TIFF/DNG structure to extract the properties (tags) from the list above, and displays them on the screen in a structured format. Note that this is a basic parser focusing on key DNG tags; full TIFF parsing handles byte order, IFD chaining, and tag types.
Python Class for .DNG File Handling
The following Python class, DNGParser
, can open a .DNG file, decode its structure, read and print the properties (tags), and write modifications back to a new file. It uses the struct
module for binary parsing and focuses on extracting DNG-specific tags.
import struct
import os
class DNGParser:
def __init__(self, filepath):
self.filepath = filepath
self.data = None
self.little_endian = True
self.tags = {}
self.read_file()
def read_file(self):
with open(self.filepath, 'rb') as f:
self.data = f.read()
byte_order = self.data[0:2].decode('ascii')
self.little_endian = byte_order == 'II'
endian = '<' if self.little_endian else '>'
magic = struct.unpack(endian + 'H', self.data[2:4])[0]
if magic != 42:
raise ValueError("Invalid TIFF magic number")
self.parse_ifds(struct.unpack(endian + 'I', self.data[4:8])[0])
def parse_ifds(self, offset):
endian = '<' if self.little_endian else '>'
while offset != 0:
num_entries = struct.unpack(endian + 'H', self.data[offset:offset+2])[0]
offset += 2
for _ in range(num_entries):
entry = self.data[offset:offset+12]
tag, typ, count, val_offset = struct.unpack(endian + 'HHI I', entry)
if tag in self.get_dng_tag_map():
value = self.read_tag_value(typ, count, val_offset)
self.tags[self.get_dng_tag_map()[tag]] = value
offset += 12
offset = struct.unpack(endian + 'I', self.data[offset:offset+4])[0]
def read_tag_value(self, typ, count, offset):
endian = '<' if self.little_endian else '>'
if typ == 1 or typ == 7: # BYTE/UNDEF
fmt = 'B' * count
elif typ == 2: # ASCII
return self.data[offset:offset+count].decode('ascii').rstrip('\x00')
elif typ == 3: # SHORT
fmt = 'H' * count
elif typ == 4: # LONG
fmt = 'I' * count
elif typ == 5: # RATIONAL
fmt = 'II' * count
values = struct.unpack(endian + fmt, self.data[offset:offset+8*count])
return [(values[i], values[i+1]) for i in range(0, len(values), 2)]
# Add more types as needed
else:
return None
return struct.unpack(endian + fmt, self.data[offset:offset + struct.calcsize(fmt)])
def get_dng_tag_map(self):
# Map of tag IDs to names (from list above)
return {
50706: 'DNGVersion',
50707: 'DNGBackwardVersion',
# Include all tags from the list similarly...
51182: 'EnhanceParams'
}
def print_properties(self):
for name, value in self.tags.items():
print(f"{name}: {value}")
def write_file(self, output_path):
# Basic write: copy data and modify if needed (expand for full write support)
with open(output_path, 'wb') as f:
f.write(self.data)
print(f"File written to {output_path}")
# Example usage:
# parser = DNGParser('example.dng')
# parser.print_properties()
# parser.write_file('modified.dng')
Java Class for .DNG File Handling
The following Java class, DNGParser
, handles opening, decoding, reading, printing, and writing .DNG files. It uses ByteBuffer
for binary parsing.
import java.io.*;
import java.nio.*;
import java.util.*;
public class DNGParser {
private String filepath;
private byte[] data;
private boolean littleEndian;
private Map<String, Object> tags = new HashMap<>();
public DNGParser(String filepath) {
this.filepath = filepath;
readFile();
}
private void readFile() {
try (FileInputStream fis = new FileInputStream(filepath)) {
data = fis.readAllBytes();
} catch (IOException e) {
e.printStackTrace();
}
ByteBuffer bb = ByteBuffer.wrap(data);
String byteOrder = new String(data, 0, 2);
littleEndian = byteOrder.equals("II");
bb.order(littleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
short magic = bb.getShort(2);
if (magic != 42) {
throw new IllegalArgumentException("Invalid TIFF magic number");
}
parseIFDs(bb.getInt(4));
}
private void parseIFDs(int offset) {
ByteBuffer bb = ByteBuffer.wrap(data).order(littleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
while (offset != 0) {
short numEntries = bb.getShort(offset);
offset += 2;
for (int i = 0; i < numEntries; i++) {
int entryOffset = offset + i * 12;
short tag = bb.getShort(entryOffset);
short typ = bb.getShort(entryOffset + 2);
int count = bb.getInt(entryOffset + 4);
int valOffset = bb.getInt(entryOffset + 8);
String tagName = getDNGTagMap().get((int) tag);
if (tagName != null) {
Object value = readTagValue(bb, typ, count, valOffset);
tags.put(tagName, value);
}
}
offset += numEntries * 12;
offset = bb.getInt(offset);
}
}
private Object readTagValue(ByteBuffer bb, short typ, int count, int offset) {
bb.position(offset);
if (typ == 1 || typ == 7) { // BYTE/UNDEF
byte[] vals = new byte[count];
bb.get(vals);
return Arrays.toString(vals);
} else if (typ == 2) { // ASCII
byte[] bytes = new byte[count];
bb.get(bytes);
return new String(bytes).trim();
} else if (typ == 3) { // SHORT
return bb.getShort();
} else if (typ == 4) { // LONG
return bb.getInt();
} else if (typ == 5) { // RATIONAL
long num = bb.getInt() & 0xFFFFFFFFL;
long den = bb.getInt() & 0xFFFFFFFFL;
return num + "/" + den;
} // Add more types
return null;
}
private Map<Integer, String> getDNGTagMap() {
Map<Integer, String> map = new HashMap<>();
map.put(50706, "DNGVersion");
map.put(50707, "DNGBackwardVersion");
// Include all tags from the list...
map.put(51182, "EnhanceParams");
return map;
}
public void printProperties() {
for (Map.Entry<String, Object> entry : tags.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void writeFile(String outputPath) throws IOException {
// Basic write: copy data (expand for modifications)
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
fos.write(data);
}
System.out.println("File written to " + outputPath);
}
// Example usage:
// public static void main(String[] args) {
// DNGParser parser = new DNGParser("example.dng");
// parser.printProperties();
// parser.writeFile("modified.dng");
// }
}
JavaScript Class for .DNG File Handling
The following JavaScript class, DNGParser
, can handle .DNG files in a browser or Node.js environment (assuming Node.js for file I/O). It decodes, reads, prints to console, and writes files.
const fs = require('fs'); // For Node.js
class DNGParser {
constructor(filepath) {
this.filepath = filepath;
this.data = null;
this.littleEndian = true;
this.tags = {};
this.readFile();
}
readFile() {
this.data = fs.readFileSync(this.filepath);
const byteOrder = String.fromCharCode(this.data[0], this.data[1]);
this.littleEndian = byteOrder === 'II';
const dv = new DataView(this.data.buffer);
const magic = dv.getUint16(2, this.littleEndian);
if (magic !== 42) {
throw new Error('Invalid TIFF magic number');
}
this.parseIFDs(dv.getUint32(4, this.littleEndian), dv);
}
parseIFDs(offset, dv) {
while (offset !== 0) {
const numEntries = dv.getUint16(offset, this.littleEndian);
offset += 2;
for (let i = 0; i < numEntries; i++) {
const entryOffset = offset + i * 12;
const tag = dv.getUint16(entryOffset, this.littleEndian);
const typ = dv.getUint16(entryOffset + 2, this.littleEndian);
const count = dv.getUint32(entryOffset + 4, this.littleEndian);
const valOffset = dv.getUint32(entryOffset + 8, this.littleEndian);
const tagName = this.getDNGTagMap()[tag];
if (tagName) {
const value = this.readTagValue(dv, typ, count, valOffset);
this.tags[tagName] = value;
}
}
offset += numEntries * 12;
offset = dv.getUint32(offset, this.littleEndian);
}
}
readTagValue(dv, typ, count, offset) {
if (typ === 1 || typ === 7) {
let vals = [];
for (let i = 0; i < count; i++) vals.push(dv.getUint8(offset + i));
return vals;
} else if (typ === 2) {
let str = '';
for (let i = 0; i < count; i++) str += String.fromCharCode(dv.getUint8(offset + i));
return str.trim();
} else if (typ === 3) {
return dv.getUint16(offset, this.littleEndian);
} else if (typ === 4) {
return dv.getUint32(offset, this.littleEndian);
} else if (typ === 5) {
const num = dv.getUint32(offset, this.littleEndian);
const den = dv.getUint32(offset + 4, this.littleEndian);
return `${num}/${den}`;
} // Add more
return null;
}
getDNGTagMap() {
return {
50706: 'DNGVersion',
50707: 'DNGBackwardVersion',
// Include all...
51182: 'EnhanceParams'
};
}
printProperties() {
for (const [name, value] of Object.entries(this.tags)) {
console.log(`${name}: ${value}`);
}
}
writeFile(outputPath) {
fs.writeFileSync(outputPath, this.data);
console.log(`File written to ${outputPath}`);
}
}
// Example usage:
// const parser = new DNGParser('example.dng');
// parser.printProperties();
// parser.writeFile('modified.dng');
C++ Class for .DNG File Handling
The following C++ class, DNGParser
, opens, decodes, reads, prints to console, and writes .DNG files. It uses std::ifstream
and std::ofstream
for I/O and manual byte parsing.
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <cstdint>
class DNGParser {
private:
std::string filepath;
std::vector<uint8_t> data;
bool littleEndian;
std::map<std::string, std::string> tags;
uint16_t readUint16(size_t offset) {
uint16_t val = (data[offset] | (data[offset+1] << 8));
if (!littleEndian) val = (data[offset+1] | (data[offset] << 8));
return val;
}
uint32_t readUint32(size_t offset) {
uint32_t val = (data[offset] | (data[offset+1] << 8) | (data[offset+2] << 16) | (data[offset+3] << 24));
if (!littleEndian) val = (data[offset+3] | (data[offset+2] << 8) | (data[offset+1] << 16) | (data[offset] << 24));
return val;
}
public:
DNGParser(const std::string& filepath) : filepath(filepath) {
std::ifstream file(filepath, std::ios::binary);
if (file) {
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
data.resize(size);
file.read(reinterpret_cast<char*>(data.data()), size);
}
std::string byteOrder(reinterpret_cast<char*>(data.data()), 2);
littleEndian = (byteOrder == "II");
uint16_t magic = readUint16(2);
if (magic != 42) {
throw std::runtime_error("Invalid TIFF magic number");
}
parseIFDs(readUint32(4));
}
void parseIFDs(uint32_t offset) {
while (offset != 0) {
uint16_t numEntries = readUint16(offset);
offset += 2;
for (uint16_t i = 0; i < numEntries; ++i) {
size_t entryOffset = offset + i * 12;
uint16_t tag = readUint16(entryOffset);
uint16_t typ = readUint16(entryOffset + 2);
uint32_t count = readUint32(entryOffset + 4);
uint32_t valOffset = readUint32(entryOffset + 8);
auto it = getDNGTagMap().find(tag);
if (it != getDNGTagMap().end()) {
std::string value = readTagValue(typ, count, valOffset);
tags[it->second] = value;
}
}
offset += numEntries * 12;
offset = readUint32(offset);
}
}
std::string readTagValue(uint16_t typ, uint32_t count, uint32_t offset) {
std::string val;
if (typ == 1 || typ == 7) { // BYTE/UNDEF
for (uint32_t i = 0; i < count; ++i) {
val += std::to_string(data[offset + i]) + " ";
}
} else if (typ == 2) { // ASCII
val = std::string(reinterpret_cast<char*>(data.data() + offset), count).c_str();
} else if (typ == 3) { // SHORT
val = std::to_string(readUint16(offset));
} else if (typ == 4) { // LONG
val = std::to_string(readUint32(offset));
} else if (typ == 5) { // RATIONAL
uint32_t num = readUint32(offset);
uint32_t den = readUint32(offset + 4);
val = std::to_string(num) + "/" + std::to_string(den);
} // Add more
return val;
}
std::map<uint16_t, std::string> getDNGTagMap() {
std::map<uint16_t, std::string> map;
map[50706] = "DNGVersion";
map[50707] = "DNGBackwardVersion";
// Include all...
map[51182] = "EnhanceParams";
return map;
}
void printProperties() {
for (const auto& pair : tags) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
}
void writeFile(const std::string& outputPath) {
std::ofstream file(outputPath, std::ios::binary);
file.write(reinterpret_cast<const char*>(data.data()), data.size());
std::cout << "File written to " << outputPath << std::endl;
}
};
// Example usage:
// int main() {
// DNGParser parser("example.dng");
// parser.printProperties();
// parser.writeFile("modified.dng");
// return 0;
// }