|
|
@ -9,30 +9,6 @@ namespace ImageProcessorCore.Formats |
|
|
|
|
|
|
|
|
internal class JpegEncoderCore |
|
|
internal class JpegEncoderCore |
|
|
{ |
|
|
{ |
|
|
private const int sof0Marker = 0xc0; // Start Of Frame (Baseline).
|
|
|
|
|
|
|
|
|
|
|
|
private const int sof1Marker = 0xc1; // Start Of Frame (Extended Sequential).
|
|
|
|
|
|
|
|
|
|
|
|
private const int sof2Marker = 0xc2; // Start Of Frame (Progressive).
|
|
|
|
|
|
|
|
|
|
|
|
private const int dhtMarker = 0xc4; // Define Huffman Table.
|
|
|
|
|
|
|
|
|
|
|
|
private const int rst0Marker = 0xd0; // ReSTart (0).
|
|
|
|
|
|
|
|
|
|
|
|
private const int rst7Marker = 0xd7; // ReSTart (7).
|
|
|
|
|
|
|
|
|
|
|
|
private const int soiMarker = 0xd8; // Start Of Image.
|
|
|
|
|
|
|
|
|
|
|
|
private const int eoiMarker = 0xd9; // End Of Image.
|
|
|
|
|
|
|
|
|
|
|
|
private const int sosMarker = 0xda; // Start Of Scan.
|
|
|
|
|
|
|
|
|
|
|
|
private const int dqtMarker = 0xdb; // Define Quantization Table.
|
|
|
|
|
|
|
|
|
|
|
|
private const int driMarker = 0xdd; // Define Restart Interval.
|
|
|
|
|
|
|
|
|
|
|
|
private const int comMarker = 0xfe; // COMment.
|
|
|
|
|
|
|
|
|
|
|
|
// "APPlication specific" markers aren't part of the JPEG spec per se,
|
|
|
// "APPlication specific" markers aren't part of the JPEG spec per se,
|
|
|
// but in practice, their use is described at
|
|
|
// but in practice, their use is described at
|
|
|
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html
|
|
|
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html
|
|
|
@ -62,12 +38,12 @@ namespace ImageProcessorCore.Formats |
|
|
// unzig[3] is the column and row of the fourth element in zig-zag order. The
|
|
|
// unzig[3] is the column and row of the fourth element in zig-zag order. The
|
|
|
// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
|
|
|
// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
|
|
|
private static readonly int[] unzig = new[] |
|
|
private static readonly int[] unzig = new[] |
|
|
{ |
|
|
{ |
|
|
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, |
|
|
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, |
|
|
33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, |
|
|
33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, |
|
|
50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, |
|
|
50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, |
|
|
39, 46, 53, 60, 61, 54, 47, 55, 62, 63, |
|
|
39, 46, 53, 60, 61, 54, 47, 55, 62, 63, |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
private const int nQuantIndex = 2; |
|
|
private const int nQuantIndex = 2; |
|
|
|
|
|
|
|
|
@ -242,7 +218,7 @@ namespace ImageProcessorCore.Formats |
|
|
private Stream outputStream; |
|
|
private Stream outputStream; |
|
|
|
|
|
|
|
|
// buf is a scratch buffer.
|
|
|
// buf is a scratch buffer.
|
|
|
private byte[] buf = new byte[16]; |
|
|
private byte[] buffer = new byte[16]; |
|
|
|
|
|
|
|
|
// bits and nBits are accumulated bits to write to w.
|
|
|
// bits and nBits are accumulated bits to write to w.
|
|
|
private uint bits; |
|
|
private uint bits; |
|
|
@ -311,104 +287,6 @@ namespace ImageProcessorCore.Formats |
|
|
if (nBits > 0) this.emit((uint)b & (uint)((1 << ((int)nBits)) - 1), nBits); |
|
|
if (nBits > 0) this.emit((uint)b & (uint)((1 << ((int)nBits)) - 1), nBits); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// writeMarkerHeader writes the header for a marker with the given length.
|
|
|
|
|
|
private void writeMarkerHeader(byte marker, int markerlen) |
|
|
|
|
|
{ |
|
|
|
|
|
this.buf[0] = 0xff; |
|
|
|
|
|
this.buf[1] = marker; |
|
|
|
|
|
this.buf[2] = (byte)(markerlen >> 8); |
|
|
|
|
|
this.buf[3] = (byte)(markerlen & 0xff); |
|
|
|
|
|
this.outputStream.Write(this.buf, 0, 4); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// writeDQT writes the Define Quantization Table marker.
|
|
|
|
|
|
private void writeDQT() |
|
|
|
|
|
{ |
|
|
|
|
|
int markerlen = 2 + nQuantIndex * (1 + Block.BlockSize); |
|
|
|
|
|
this.writeMarkerHeader(dqtMarker, markerlen); |
|
|
|
|
|
for (int i = 0; i < nQuantIndex; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
this.writeByte((byte)i); |
|
|
|
|
|
this.outputStream.Write(this.quant[i], 0, this.quant[i].Length); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// writeSOF0 writes the Start Of Frame (Baseline) marker.
|
|
|
|
|
|
private void writeSOF0(int wid, int hei, int nComponent) |
|
|
|
|
|
{ |
|
|
|
|
|
// "default" to 4:2:0
|
|
|
|
|
|
byte[] subsamples = { 0x22, 0x11, 0x11 }; |
|
|
|
|
|
byte[] chroma = { 0x00, 0x01, 0x01 }; |
|
|
|
|
|
|
|
|
|
|
|
switch (this.subsample) |
|
|
|
|
|
{ |
|
|
|
|
|
case JpegSubsample.Ratio444: |
|
|
|
|
|
subsamples = new byte[] { 0x11, 0x11, 0x11 }; |
|
|
|
|
|
break; |
|
|
|
|
|
case JpegSubsample.Ratio420: |
|
|
|
|
|
subsamples = new byte[] { 0x22, 0x11, 0x11 }; |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int markerlen = 8 + 3 * nComponent; |
|
|
|
|
|
this.writeMarkerHeader(sof0Marker, markerlen); |
|
|
|
|
|
this.buf[0] = 8; // 8-bit color.
|
|
|
|
|
|
this.buf[1] = (byte)(hei >> 8); |
|
|
|
|
|
this.buf[2] = (byte)(hei & 0xff); |
|
|
|
|
|
this.buf[3] = (byte)(wid >> 8); |
|
|
|
|
|
this.buf[4] = (byte)(wid & 0xff); |
|
|
|
|
|
this.buf[5] = (byte)nComponent; |
|
|
|
|
|
if (nComponent == 1) |
|
|
|
|
|
{ |
|
|
|
|
|
this.buf[6] = 1; |
|
|
|
|
|
|
|
|
|
|
|
// No subsampling for grayscale image.
|
|
|
|
|
|
this.buf[7] = 0x11; |
|
|
|
|
|
this.buf[8] = 0x00; |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
for (int i = 0; i < nComponent; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
this.buf[3 * i + 6] = (byte)(i + 1); |
|
|
|
|
|
|
|
|
|
|
|
// We use 4:2:0 chroma subsampling.
|
|
|
|
|
|
this.buf[3 * i + 7] = subsamples[i]; |
|
|
|
|
|
this.buf[3 * i + 8] = chroma[i]; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.outputStream.Write(this.buf, 0, 3 * (nComponent - 1) + 9); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// writeDHT writes the Define Huffman Table marker.
|
|
|
|
|
|
private void writeDHT(int nComponent) |
|
|
|
|
|
{ |
|
|
|
|
|
byte[] headers = new byte[] { 0x00, 0x10, 0x01, 0x11 }; |
|
|
|
|
|
int markerlen = 2; |
|
|
|
|
|
huffmanSpec[] specs = this.theHuffmanSpec; |
|
|
|
|
|
|
|
|
|
|
|
if (nComponent == 1) |
|
|
|
|
|
{ |
|
|
|
|
|
// Drop the Chrominance tables.
|
|
|
|
|
|
specs = new[] { this.theHuffmanSpec[0], this.theHuffmanSpec[1] }; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
foreach (var s in specs) |
|
|
|
|
|
{ |
|
|
|
|
|
markerlen += 1 + 16 + s.values.Length; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.writeMarkerHeader(dhtMarker, markerlen); |
|
|
|
|
|
for (int i = 0; i < specs.Length; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
var s = specs[i]; |
|
|
|
|
|
|
|
|
|
|
|
this.writeByte(headers[i]); |
|
|
|
|
|
this.outputStream.Write(s.count, 0, s.count.Length); |
|
|
|
|
|
this.outputStream.Write(s.values, 0, s.values.Length); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// writeBlock writes a block of pixel data using the given quantization table,
|
|
|
// writeBlock writes a block of pixel data using the given quantization table,
|
|
|
// returning the post-quantized DC value of the DCT-transformed block. b is in
|
|
|
// returning the post-quantized DC value of the DCT-transformed block. b is in
|
|
|
@ -418,7 +296,7 @@ namespace ImageProcessorCore.Formats |
|
|
FDCT.Transform(b); |
|
|
FDCT.Transform(b); |
|
|
|
|
|
|
|
|
// Emit the DC delta.
|
|
|
// Emit the DC delta.
|
|
|
int dc = div(b[0], 8 * this.quant[(int)q][0]); |
|
|
int dc = Round(b[0], 8 * this.quant[(int)q][0]); |
|
|
this.emitHuffRLE((huffIndex)(2 * (int)q + 0), 0, dc - prevDC); |
|
|
this.emitHuffRLE((huffIndex)(2 * (int)q + 0), 0, dc - prevDC); |
|
|
|
|
|
|
|
|
// Emit the AC components.
|
|
|
// Emit the AC components.
|
|
|
@ -427,7 +305,7 @@ namespace ImageProcessorCore.Formats |
|
|
|
|
|
|
|
|
for (int zig = 1; zig < Block.BlockSize; zig++) |
|
|
for (int zig = 1; zig < Block.BlockSize; zig++) |
|
|
{ |
|
|
{ |
|
|
int ac = div(b[unzig[zig]], 8 * this.quant[(int)q][zig]); |
|
|
int ac = Round(b[unzig[zig]], 8 * this.quant[(int)q][zig]); |
|
|
|
|
|
|
|
|
if (ac == 0) |
|
|
if (ac == 0) |
|
|
{ |
|
|
{ |
|
|
@ -488,16 +366,26 @@ namespace ImageProcessorCore.Formats |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// sosHeaderY is the SOS marker "\xff\xda" followed by 8 bytes:
|
|
|
// The SOS marker "\xff\xda" followed by 8 bytes:
|
|
|
// - the marker length "\x00\x08",
|
|
|
// - the marker length "\x00\x08",
|
|
|
// - the number of components "\x01",
|
|
|
// - the number of components "\x01",
|
|
|
// - component 1 uses DC table 0 and AC table 0 "\x01\x00",
|
|
|
// - component 1 uses DC table 0 and AC table 0 "\x01\x00",
|
|
|
// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for
|
|
|
// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for
|
|
|
// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al)
|
|
|
// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al)
|
|
|
// should be 0x00, 0x3f, 0x00<<4 | 0x00.
|
|
|
// should be 0x00, 0x3f, 0x00<<4 | 0x00.
|
|
|
private readonly byte[] sosHeaderY = new byte[] { 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, }; |
|
|
private readonly byte[] SOSHeaderY = |
|
|
|
|
|
{ |
|
|
|
|
|
JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, |
|
|
|
|
|
0x00, 0x08, // Length (high byte, low byte), must be 6 + 2 * (number of components in scan)
|
|
|
|
|
|
0x01, // Number of components in a scan, 1
|
|
|
|
|
|
0x01, // Component Id Y
|
|
|
|
|
|
0x00, // DC/AC Huffman table
|
|
|
|
|
|
0x00, // Ss - Start of spectral selection.
|
|
|
|
|
|
0x3f, // Se - End of spectral selection.
|
|
|
|
|
|
0x00 // Ah + Ah (Successive approximation bit position high + low)
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// sosHeaderYCbCr is the SOS marker "\xff\xda" followed by 12 bytes:
|
|
|
// The SOS marker "\xff\xda" followed by 12 bytes:
|
|
|
// - the marker length "\x00\x0c",
|
|
|
// - the marker length "\x00\x0c",
|
|
|
// - the number of components "\x03",
|
|
|
// - the number of components "\x03",
|
|
|
// - component 1 uses DC table 0 and AC table 0 "\x01\x00",
|
|
|
// - component 1 uses DC table 0 and AC table 0 "\x01\x00",
|
|
|
@ -506,81 +394,22 @@ namespace ImageProcessorCore.Formats |
|
|
// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for
|
|
|
// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for
|
|
|
// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al)
|
|
|
// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al)
|
|
|
// should be 0x00, 0x3f, 0x00<<4 | 0x00.
|
|
|
// should be 0x00, 0x3f, 0x00<<4 | 0x00.
|
|
|
private readonly byte[] sosHeaderYCbCr = new byte[] |
|
|
private readonly byte[] SOSHeaderYCbCr = |
|
|
{ |
|
|
{ |
|
|
0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, |
|
|
JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, |
|
|
0x00, 0x3f, 0x00, |
|
|
0x00, 0x0c, // Length (high byte, low byte), must be 6 + 2 * (number of components in scan)
|
|
|
|
|
|
0x03, // Number of components in a scan, 3
|
|
|
|
|
|
0x01, // Component Id Y
|
|
|
|
|
|
0x00, // DC/AC Huffman table
|
|
|
|
|
|
0x02, // Component Id Cb
|
|
|
|
|
|
0x11, // DC/AC Huffman table
|
|
|
|
|
|
0x03, // Component Id Cr
|
|
|
|
|
|
0x11, // DC/AC Huffman table
|
|
|
|
|
|
0x00, // Ss - Start of spectral selection.
|
|
|
|
|
|
0x3f, // Se - End of spectral selection.
|
|
|
|
|
|
0x00 // Ah + Ah (Successive approximation bit position high + low)
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// writeSOS writes the StartOfScan marker.
|
|
|
|
|
|
private void writeSOS(PixelAccessor pixels) |
|
|
|
|
|
{ |
|
|
|
|
|
this.outputStream.Write(this.sosHeaderYCbCr, 0, this.sosHeaderYCbCr.Length); |
|
|
|
|
|
|
|
|
|
|
|
switch (this.subsample) |
|
|
|
|
|
{ |
|
|
|
|
|
case JpegSubsample.Ratio444: |
|
|
|
|
|
this.encode444(pixels); |
|
|
|
|
|
break; |
|
|
|
|
|
case JpegSubsample.Ratio420: |
|
|
|
|
|
this.encode420(pixels); |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Pad the last byte with 1's.
|
|
|
|
|
|
this.emit(0x7f, 7); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void encode444(PixelAccessor pixels) |
|
|
|
|
|
{ |
|
|
|
|
|
Block b = new Block(); |
|
|
|
|
|
Block cb = new Block(); |
|
|
|
|
|
Block cr = new Block(); |
|
|
|
|
|
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; |
|
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < pixels.Height; y += 8) |
|
|
|
|
|
{ |
|
|
|
|
|
for (int x = 0; x < pixels.Width; x += 8) |
|
|
|
|
|
{ |
|
|
|
|
|
this.toYCbCr(pixels, x, y, b, cb, cr); |
|
|
|
|
|
prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); |
|
|
|
|
|
prevDCCb = this.writeBlock(cb, (quantIndex)1, prevDCCb); |
|
|
|
|
|
prevDCCr = this.writeBlock(cr, (quantIndex)1, prevDCCr); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void encode420(PixelAccessor pixels) |
|
|
|
|
|
{ |
|
|
|
|
|
Block b = new Block(); |
|
|
|
|
|
Block[] cb = new Block[4]; |
|
|
|
|
|
Block[] cr = new Block[4]; |
|
|
|
|
|
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; |
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++) cb[i] = new Block(); |
|
|
|
|
|
for (int i = 0; i < 4; i++) cr[i] = new Block(); |
|
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < pixels.Height; y += 16) |
|
|
|
|
|
{ |
|
|
|
|
|
for (int x = 0; x < pixels.Width; x += 16) |
|
|
|
|
|
{ |
|
|
|
|
|
for (int i = 0; i < 4; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
int xOff = (i & 1) * 8; |
|
|
|
|
|
int yOff = (i & 2) * 4; |
|
|
|
|
|
|
|
|
|
|
|
this.toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); |
|
|
|
|
|
prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.scale_16x16_8x8(b, cb); |
|
|
|
|
|
prevDCCb = this.writeBlock(b, (quantIndex)1, prevDCCb); |
|
|
|
|
|
this.scale_16x16_8x8(b, cr); |
|
|
|
|
|
prevDCCr = this.writeBlock(b, (quantIndex)1, prevDCCr); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Encode writes the Image m to w in JPEG 4:2:0 baseline format with the given
|
|
|
// Encode writes the Image m to w in JPEG 4:2:0 baseline format with the given
|
|
|
// options. Default parameters are used if a nil *Options is passed.
|
|
|
// options. Default parameters are used if a nil *Options is passed.
|
|
|
public void Encode(Stream stream, ImageBase image, int quality, JpegSubsample sample) |
|
|
public void Encode(Stream stream, ImageBase image, int quality, JpegSubsample sample) |
|
|
@ -636,40 +465,247 @@ namespace ImageProcessorCore.Formats |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Compute number of components based on input image type.
|
|
|
// Compute number of components based on input image type.
|
|
|
int nComponent = 3; |
|
|
int componentCount = 3; |
|
|
|
|
|
|
|
|
// Write the Start Of Image marker.
|
|
|
// Write the Start Of Image marker.
|
|
|
this.buf[0] = 0xff; |
|
|
// TODO: JFIF header etc.
|
|
|
this.buf[1] = 0xd8; |
|
|
this.buffer[0] = 0xff; |
|
|
stream.Write(this.buf, 0, 2); |
|
|
this.buffer[1] = 0xd8; |
|
|
|
|
|
stream.Write(this.buffer, 0, 2); |
|
|
|
|
|
|
|
|
// Write the quantization tables.
|
|
|
// Write the quantization tables.
|
|
|
this.writeDQT(); |
|
|
this.WriteDQT(); |
|
|
|
|
|
|
|
|
// Write the image dimensions.
|
|
|
// Write the image dimensions.
|
|
|
this.writeSOF0(image.Width, image.Height, nComponent); |
|
|
this.WriteSOF0(image.Width, image.Height, componentCount); |
|
|
|
|
|
|
|
|
// Write the Huffman tables.
|
|
|
// Write the Huffman tables.
|
|
|
this.writeDHT(nComponent); |
|
|
this.WriteDHT(componentCount); |
|
|
|
|
|
|
|
|
// Write the image data.
|
|
|
// Write the image data.
|
|
|
using (PixelAccessor pixels = image.Lock()) |
|
|
using (PixelAccessor pixels = image.Lock()) |
|
|
{ |
|
|
{ |
|
|
this.writeSOS(pixels); |
|
|
this.WriteSOS(pixels); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Write the End Of Image marker.
|
|
|
// Write the End Of Image marker.
|
|
|
this.buf[0] = 0xff; |
|
|
this.buffer[0] = 0xff; |
|
|
this.buf[1] = 0xd9; |
|
|
this.buffer[1] = 0xd9; |
|
|
stream.Write(this.buf, 0, 2); |
|
|
stream.Write(this.buffer, 0, 2); |
|
|
stream.Flush(); |
|
|
stream.Flush(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// div returns a/b rounded to the nearest integer, instead of rounded to zero.
|
|
|
/// <summary>
|
|
|
private static int div(int a, int b) |
|
|
/// Gets the quotient of the two numbers rounded to the nearest integer, instead of rounded to zero.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dividend">The value to divide.</param>
|
|
|
|
|
|
/// <param name="divisor">The value to divide by.</param>
|
|
|
|
|
|
/// <returns>The <see cref="int"/></returns>
|
|
|
|
|
|
private static int Round(int dividend, int divisor) |
|
|
|
|
|
{ |
|
|
|
|
|
if (dividend >= 0) |
|
|
|
|
|
{ |
|
|
|
|
|
return (dividend + (divisor >> 1)) / divisor; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return -((-dividend + (divisor >> 1)) / divisor); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Writes the Define Quantization Marker and tables.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void WriteDQT() |
|
|
|
|
|
{ |
|
|
|
|
|
int markerlen = 2 + nQuantIndex * (1 + Block.BlockSize); |
|
|
|
|
|
this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); |
|
|
|
|
|
for (int i = 0; i < nQuantIndex; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
this.writeByte((byte)i); |
|
|
|
|
|
this.outputStream.Write(this.quant[i], 0, this.quant[i].Length); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Writes the Start Of Frame (Baseline) marker
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="width">The width of the image</param>
|
|
|
|
|
|
/// <param name="height">The height of the image</param>
|
|
|
|
|
|
/// <param name="componentCount"></param>
|
|
|
|
|
|
private void WriteSOF0(int width, int height, int componentCount) |
|
|
|
|
|
{ |
|
|
|
|
|
// "default" to 4:2:0
|
|
|
|
|
|
byte[] subsamples = { 0x22, 0x11, 0x11 }; |
|
|
|
|
|
byte[] chroma = { 0x00, 0x01, 0x01 }; |
|
|
|
|
|
|
|
|
|
|
|
switch (this.subsample) |
|
|
|
|
|
{ |
|
|
|
|
|
case JpegSubsample.Ratio444: |
|
|
|
|
|
subsamples = new byte[] { 0x11, 0x11, 0x11 }; |
|
|
|
|
|
break; |
|
|
|
|
|
case JpegSubsample.Ratio420: |
|
|
|
|
|
subsamples = new byte[] { 0x22, 0x11, 0x11 }; |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Length (high byte, low byte), 8 + components * 3.
|
|
|
|
|
|
int markerlen = 8 + 3 * componentCount; |
|
|
|
|
|
this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen); |
|
|
|
|
|
this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported
|
|
|
|
|
|
this.buffer[1] = (byte)(height >> 8); |
|
|
|
|
|
this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
|
|
|
|
|
|
this.buffer[3] = (byte)(width >> 8); |
|
|
|
|
|
this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
|
|
|
|
|
|
this.buffer[5] = (byte)componentCount; // Number of components (1 byte), usually 1 = grey scaled, 3 = color YCbCr or YIQ, 4 = color CMYK)
|
|
|
|
|
|
if (componentCount == 1) |
|
|
|
|
|
{ |
|
|
|
|
|
this.buffer[6] = 1; |
|
|
|
|
|
|
|
|
|
|
|
// No subsampling for grayscale images.
|
|
|
|
|
|
this.buffer[7] = 0x11; |
|
|
|
|
|
this.buffer[8] = 0x00; |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
for (int i = 0; i < componentCount; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
this.buffer[3 * i + 6] = (byte)(i + 1); |
|
|
|
|
|
|
|
|
|
|
|
// We use 4:2:0 chroma subsampling by default.
|
|
|
|
|
|
this.buffer[3 * i + 7] = subsamples[i]; |
|
|
|
|
|
this.buffer[3 * i + 8] = chroma[i]; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.outputStream.Write(this.buffer, 0, 3 * (componentCount - 1) + 9); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Writes the Define Huffman Table marker and tables.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="nComponent">The number of components to write.</param>
|
|
|
|
|
|
private void WriteDHT(int nComponent) |
|
|
|
|
|
{ |
|
|
|
|
|
byte[] headers = { 0x00, 0x10, 0x01, 0x11 }; |
|
|
|
|
|
int markerlen = 2; |
|
|
|
|
|
huffmanSpec[] specs = this.theHuffmanSpec; |
|
|
|
|
|
|
|
|
|
|
|
if (nComponent == 1) |
|
|
|
|
|
{ |
|
|
|
|
|
// Drop the Chrominance tables.
|
|
|
|
|
|
specs = new[] { this.theHuffmanSpec[0], this.theHuffmanSpec[1] }; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
foreach (var s in specs) |
|
|
|
|
|
{ |
|
|
|
|
|
markerlen += 1 + 16 + s.values.Length; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); |
|
|
|
|
|
for (int i = 0; i < specs.Length; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
huffmanSpec spec = specs[i]; |
|
|
|
|
|
|
|
|
|
|
|
this.writeByte(headers[i]); |
|
|
|
|
|
this.outputStream.Write(spec.count, 0, spec.count.Length); |
|
|
|
|
|
this.outputStream.Write(spec.values, 0, spec.values.Length); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Writes the StartOfScan marker.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="pixels">The pixel accessor providing acces to the image pixels.</param>
|
|
|
|
|
|
private void WriteSOS(PixelAccessor pixels) |
|
|
|
|
|
{ |
|
|
|
|
|
// TODO: We should allow grayscale writing.
|
|
|
|
|
|
this.outputStream.Write(this.SOSHeaderYCbCr, 0, this.SOSHeaderYCbCr.Length); |
|
|
|
|
|
|
|
|
|
|
|
switch (this.subsample) |
|
|
|
|
|
{ |
|
|
|
|
|
case JpegSubsample.Ratio444: |
|
|
|
|
|
this.Encode444(pixels); |
|
|
|
|
|
break; |
|
|
|
|
|
case JpegSubsample.Ratio420: |
|
|
|
|
|
this.Encode420(pixels); |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Pad the last byte with 1's.
|
|
|
|
|
|
this.emit(0x7f, 7); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Encodes the image with no subsampling.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="pixels">The pixel accessor providing acces to the image pixels.</param>
|
|
|
|
|
|
private void Encode444(PixelAccessor pixels) |
|
|
|
|
|
{ |
|
|
|
|
|
Block b = new Block(); |
|
|
|
|
|
Block cb = new Block(); |
|
|
|
|
|
Block cr = new Block(); |
|
|
|
|
|
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; |
|
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < pixels.Height; y += 8) |
|
|
|
|
|
{ |
|
|
|
|
|
for (int x = 0; x < pixels.Width; x += 8) |
|
|
|
|
|
{ |
|
|
|
|
|
this.toYCbCr(pixels, x, y, b, cb, cr); |
|
|
|
|
|
prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); |
|
|
|
|
|
prevDCCb = this.writeBlock(cb, (quantIndex)1, prevDCCb); |
|
|
|
|
|
prevDCCr = this.writeBlock(cr, (quantIndex)1, prevDCCr); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Encodes the image with subsampling. The Cb and Cr components are each subsampled
|
|
|
|
|
|
/// at a factor of 2 both horizontally and vertically.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="pixels">The pixel accessor providing acces to the image pixels.</param>
|
|
|
|
|
|
private void Encode420(PixelAccessor pixels) |
|
|
|
|
|
{ |
|
|
|
|
|
Block b = new Block(); |
|
|
|
|
|
Block[] cb = new Block[4]; |
|
|
|
|
|
Block[] cr = new Block[4]; |
|
|
|
|
|
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; |
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++) cb[i] = new Block(); |
|
|
|
|
|
for (int i = 0; i < 4; i++) cr[i] = new Block(); |
|
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < pixels.Height; y += 16) |
|
|
|
|
|
{ |
|
|
|
|
|
for (int x = 0; x < pixels.Width; x += 16) |
|
|
|
|
|
{ |
|
|
|
|
|
for (int i = 0; i < 4; i++) |
|
|
|
|
|
{ |
|
|
|
|
|
int xOff = (i & 1) * 8; |
|
|
|
|
|
int yOff = (i & 2) * 4; |
|
|
|
|
|
|
|
|
|
|
|
this.toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); |
|
|
|
|
|
prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.scale_16x16_8x8(b, cb); |
|
|
|
|
|
prevDCCb = this.writeBlock(b, (quantIndex)1, prevDCCb); |
|
|
|
|
|
this.scale_16x16_8x8(b, cr); |
|
|
|
|
|
prevDCCr = this.writeBlock(b, (quantIndex)1, prevDCCr); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Writes the header for a marker with the given length.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="marker">The marker to write.</param>
|
|
|
|
|
|
/// <param name="length">The marker length.</param>
|
|
|
|
|
|
private void WriteMarkerHeader(byte marker, int length) |
|
|
{ |
|
|
{ |
|
|
if (a >= 0) return (a + (b >> 1)) / b; |
|
|
// Markers are always prefixed with with 0xff.
|
|
|
else return -((-a + (b >> 1)) / b); |
|
|
this.buffer[0] = JpegConstants.Markers.XFF; |
|
|
|
|
|
this.buffer[1] = marker; |
|
|
|
|
|
this.buffer[2] = (byte)(length >> 8); |
|
|
|
|
|
this.buffer[3] = (byte)(length & 0xff); |
|
|
|
|
|
this.outputStream.Write(this.buffer, 0, 4); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|