Browse Source

Use in built color transforms.

Plus some cleanup.


Former-commit-id: c5b1812f853366b9f5871d0a6f679637f3dfbcff
Former-commit-id: aabf62cb1f686ad62cf4691fd83375ce18ba474b
Former-commit-id: a5afb4446cde846ab68a36ad3a6c69d840fd8850
af/merge-core
James Jackson-South 11 years ago
parent
commit
eae5800b01
  1. 6
      src/ImageProcessorCore/Colors/ColorspaceTransforms.cs
  2. 11
      src/ImageProcessorCore/Formats/Jpg/Block.cs
  3. 45
      src/ImageProcessorCore/Formats/Jpg/Colors.cs
  4. 104
      src/ImageProcessorCore/Formats/Jpg/Decoder.cs
  5. 84
      src/ImageProcessorCore/Formats/Jpg/Encoder.cs
  6. 75
      src/ImageProcessorCore/Formats/Jpg/FDCT.cs
  7. 9
      src/ImageProcessorCore/Formats/Jpg/IDCT.cs
  8. 9
      src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs
  9. 6
      src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs
  10. 3
      src/ImageProcessorCore/Formats/Jpg/README.md

6
src/ImageProcessorCore/Colors/ColorspaceTransforms.cs

@ -60,9 +60,9 @@ namespace ImageProcessorCore
float cb = color.Cb - 128; float cb = color.Cb - 128;
float cr = color.Cr - 128; float cr = color.Cr - 128;
float r = (float)(y + (1.402 * cr)) / 255f; float r = (float)(y + (1.402 * cr)).Clamp(0, 255) / 255f;
float g = (float)(y - (0.34414 * cb) - (0.71414 * cr)) / 255f; float g = (float)(y - (0.34414 * cb) - (0.71414 * cr)).Clamp(0, 255) / 255f;
float b = (float)(y + (1.772 * cb)) / 255f; float b = (float)(y + (1.772 * cb)).Clamp(0, 255) / 255f;
return new Color(r, g, b); return new Color(r, g, b);
} }

11
src/ImageProcessorCore/Formats/Jpg/Block.cs

@ -1,8 +1,5 @@
namespace ImageProcessorCore.Formats.Jpg namespace ImageProcessorCore.Formats
{ {
using System;
using System.IO;
internal class Block internal class Block
{ {
public const int blockSize = 64; public const int blockSize = 64;
@ -13,6 +10,10 @@ namespace ImageProcessorCore.Formats.Jpg
_data = new int[blockSize]; _data = new int[blockSize];
} }
public int this[int idx] { get { return _data[idx]; } set { _data[idx] = value; } } public int this[int idx]
{
get { return _data[idx]; }
set { _data[idx] = value; }
}
} }
} }

45
src/ImageProcessorCore/Formats/Jpg/Colors.cs

@ -1,45 +0,0 @@
namespace ImageProcessorCore.Formats.Jpg
{
using System;
using System.IO;
internal static class Colors
{
public static void RGBToYCbCr(byte r, byte g, byte b, out byte yy, out byte cb, out byte cr)
{
// The JFIF specification says:
// Y' = 0.2990*R + 0.5870*G + 0.1140*B
// Cb = -0.1687*R - 0.3313*G + 0.5000*B + 128
// Cr = 0.5000*R - 0.4187*G - 0.0813*B + 128
// http://www.w3.org/Graphics/JPEG/jfif3.pdf says Y but means Y'.
int iyy = (19595*r + 38470*g + 7471*b + (1<<15)) >> 16;
int icb = (-11056*r - 21712*g + 32768*b + (257<<15)) >> 16;
int icr = (32768*r - 27440*g - 5328*b + (257<<15)) >> 16;
if (iyy < 0) yy = 0; else if (iyy > 255) yy = 255; else yy = (byte)iyy;
if (icb < 0) cb = 0; else if (icb > 255) cb = 255; else cb = (byte)icb;
if (icr < 0) cr = 0; else if (icr > 255) cr = 255; else cr = (byte)icr;
}
public static void YCbCrToRGB(byte yy, byte cb, byte cr, out byte r, out byte g, out byte b)
{
// The JFIF specification says:
// R = Y' + 1.40200*(Cr-128)
// G = Y' - 0.34414*(Cb-128) - 0.71414*(Cr-128)
// B = Y' + 1.77200*(Cb-128)
// http://www.w3.org/Graphics/JPEG/jfif3.pdf says Y but means Y'.
int yy1 = yy * 0x10100; // Convert 0x12 to 0x121200.
int cb1 = cb - 128;
int cr1 = cr - 128;
int ir = (yy1 + 91881*cr1) >> 16;
int ig = (yy1 - 22554*cb1 - 46802*cr1) >> 16;
int ib = (yy1 + 116130*cb1) >> 16;
if (ir < 0) r = 0; else if (ir > 255) r = 255; else r = (byte)ir;
if (ig < 0) g = 0; else if (ig > 255) g = 255; else g = (byte)ig;
if (ib < 0) b = 0; else if (ib > 255) b = 255; else b = (byte)ib;
}
}
}

104
src/ImageProcessorCore/Formats/Jpg/Decoder.cs

@ -1,10 +1,10 @@
namespace ImageProcessorCore.Formats.Jpg namespace ImageProcessorCore.Formats
{ {
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
internal partial class Decoder internal class Decoder
{ {
private class errMissingFF00 : Exception { } private class errMissingFF00 : Exception { }
private class errShortHuffmanData : Exception { } private class errShortHuffmanData : Exception { }
@ -203,7 +203,7 @@ namespace ImageProcessorCore.Formats.Jpg
public class img_rgb public class img_rgb
{ {
public byte[] pixels; public float[] pixels;
public int stride; public int stride;
public int w, h; public int w, h;
@ -215,7 +215,7 @@ namespace ImageProcessorCore.Formats.Jpg
{ {
this.w = w; this.w = w;
this.h = h; this.h = h;
this.pixels = new byte[w * h * 3]; this.pixels = new float[w * h * 3];
this.stride = w * 3; this.stride = w * 3;
} }
@ -1062,11 +1062,20 @@ namespace ImageProcessorCore.Formats.Jpg
} }
if (img1 != null) if (img1 != null)
{
return; return;
}
else if (img3 != null) else if (img3 != null)
{ {
if (comp[0].c == 'R' && comp[1].c == 'G' && comp[2].c == 'B') imgrgb = convert_direct_to_rgb(width, height); if (comp[0].c == 'R' && comp[1].c == 'G' && comp[2].c == 'B')
else imgrgb = convert_to_rgb(width, height); {
imgrgb = convert_direct_to_rgb(width, height);
}
else
{
imgrgb = convert_to_rgb(width, height);
}
} }
else else
{ {
@ -1074,41 +1083,41 @@ namespace ImageProcessorCore.Formats.Jpg
} }
} }
private img_rgb convert_to_rgb(int w, int h) private img_rgb convert_to_rgb(int weight, int height)
{ {
var ret = new img_rgb(w, h); img_rgb ret = new img_rgb(weight, height);
int cScale = comp[0].h / comp[1].h; int cScale = comp[0].h / comp[1].h;
Parallel.For( Parallel.For(
0, 0,
h, height,
y => y =>
{ {
int po = ret.get_row_offset(y); int po = ret.get_row_offset(y);
int yo = img3.get_row_y_offset(y); int yo = img3.get_row_y_offset(y);
int co = img3.get_row_c_offset(y); int co = img3.get_row_c_offset(y);
for (int x = 0; x < w; x++) for (int x = 0; x < weight; x++)
{ {
byte yy = img3.pix_y[yo + x]; byte yy = img3.pix_y[yo + x];
byte cb = img3.pix_cb[co + x / cScale]; byte cb = img3.pix_cb[co + x / cScale];
byte cr = img3.pix_cr[co + x / cScale]; byte cr = img3.pix_cr[co + x / cScale];
int index = po + (3 * x);
byte r, g, b; // Implicit casting FTW
Colors.YCbCrToRGB(yy, cb, cr, out r, out g, out b); Color color = new YCbCr(yy, cb, cr);
ret.pixels[po + 3 * x + 0] = r; ret.pixels[index] = color.R;
ret.pixels[po + 3 * x + 1] = g; ret.pixels[index + 1] = color.G;
ret.pixels[po + 3 * x + 2] = b; ret.pixels[index + 2] = color.B;
} }
} });
);
return ret; return ret;
} }
private img_rgb convert_direct_to_rgb(int w, int h) private img_rgb convert_direct_to_rgb(int w, int h)
{ {
var ret = new img_rgb(w, h); img_rgb ret = new img_rgb(w, h);
int cScale = comp[0].h / comp[1].h; int cScale = comp[0].h / comp[1].h;
for (var y = 0; y < h; y++) for (var y = 0; y < h; y++)
@ -1116,11 +1125,17 @@ namespace ImageProcessorCore.Formats.Jpg
int po = ret.get_row_offset(y); int po = ret.get_row_offset(y);
int yo = img3.get_row_y_offset(y); int yo = img3.get_row_y_offset(y);
int co = img3.get_row_c_offset(y); int co = img3.get_row_c_offset(y);
for (int x = 0; x < w; x++) for (int x = 0; x < w; x++)
{ {
ret.pixels[po + 3 * x + 0] = img3.pix_y[yo + x]; byte red = img3.pix_y[yo + x];
ret.pixels[po + 3 * x + 1] = img3.pix_cb[co + x / cScale]; byte green = img3.pix_cb[co + x / cScale];
ret.pixels[po + 3 * x + 2] = img3.pix_cr[co + x / cScale]; byte blue = img3.pix_cr[co + x / cScale];
int index = po + (3 * x);
Color color = new Bgra32(red, green, blue);
ret.pixels[index] = color.R;
ret.pixels[index + 1] = color.G;
ret.pixels[index + 2] = color.B;
} }
} }
@ -1138,32 +1153,46 @@ namespace ImageProcessorCore.Formats.Jpg
void processSOS(int n) void processSOS(int n)
{ {
if (nComp == 0) if (nComp == 0)
throw new Exception("missing SOF marker"); {
throw new ImageFormatException("missing SOF marker");
}
if (n < 6 || 4 + 2 * nComp < n || n % 2 != 0) if (n < 6 || 4 + 2 * nComp < n || n % 2 != 0)
throw new Exception("SOS has wrong length"); {
throw new ImageFormatException("SOS has wrong length");
}
readFull(tmp, 0, n); readFull(tmp, 0, n);
nComp = tmp[0]; nComp = tmp[0];
if (n != 4 + 2 * nComp) if (n != 4 + 2 * nComp)
throw new Exception("SOS length inconsistent with number of components"); {
throw new ImageFormatException("SOS length inconsistent with number of components");
}
var scan = new scan_scruct[maxComponents]; var scan = new scan_scruct[maxComponents];
int totalHV = 0; int totalHV = 0;
for (int i = 0; i < nComp; i++) for (int i = 0; i < nComp; i++)
{ {
int cs = tmp[1 + 2 * i]; // Component selector. // Component selector.
int cs = tmp[1 + (2 * i)];
int compIndex = -1; int compIndex = -1;
for (int j = 0; j < nComp; j++) for (int j = 0; j < nComp; j++)
{ {
var compv = comp[j]; var compv = comp[j];
if (cs == compv.c) if (cs == compv.c)
{
compIndex = j; compIndex = j;
} }
}
if (compIndex < 0) if (compIndex < 0)
throw new Exception("unknown component selector"); {
throw new ImageFormatException("Unknown component selector");
}
scan[i].compIndex = (byte)compIndex; scan[i].compIndex = (byte)compIndex;
// Section B.2.3 states that "the value of Cs_j shall be different from // Section B.2.3 states that "the value of Cs_j shall be different from
// the values of Cs_1 through Cs_(j-1)". Since we have previously // the values of Cs_1 through Cs_(j-1)". Since we have previously
// verified that a frame's component identifiers (C_i values in section // verified that a frame's component identifiers (C_i values in section
@ -1172,24 +1201,32 @@ namespace ImageProcessorCore.Formats.Jpg
for (int j = 0; j < i; j++) for (int j = 0; j < i; j++)
{ {
if (scan[i].compIndex == scan[j].compIndex) if (scan[i].compIndex == scan[j].compIndex)
{
throw new Exception("repeated component selector"); throw new Exception("repeated component selector");
} }
}
totalHV += comp[compIndex].h * comp[compIndex].v; totalHV += comp[compIndex].h * comp[compIndex].v;
scan[i].td = (byte)(tmp[2 + 2 * i] >> 4); scan[i].td = (byte)(tmp[2 + 2 * i] >> 4);
if (scan[i].td > maxTh) if (scan[i].td > maxTh)
throw new Exception("bad Td value"); {
throw new ImageFormatException("bad Td value");
}
scan[i].ta = (byte)(tmp[2 + 2 * i] & 0x0f); scan[i].ta = (byte)(tmp[2 + 2 * i] & 0x0f);
if (scan[i].ta > maxTh) if (scan[i].ta > maxTh)
throw new Exception("bad Ta value"); {
throw new ImageFormatException("bad Ta value");
}
} }
// Section B.2.3 states that if there is more than one component then the // Section B.2.3 states that if there is more than one component then the
// total H*V values in a scan must be <= 10. // total H*V values in a scan must be <= 10.
if (nComp > 1 && totalHV > 10) if (nComp > 1 && totalHV > 10)
throw new Exception("total sampling factors too large"); {
throw new ImageFormatException("Total sampling factors too large.");
}
// zigStart and zigEnd are the spectral selection bounds. // zigStart and zigEnd are the spectral selection bounds.
// ah and al are the successive approximation high and low values. // ah and al are the successive approximation high and low values.
@ -1233,7 +1270,9 @@ namespace ImageProcessorCore.Formats.Jpg
int myy = (height + 8 * v0 - 1) / (8 * v0); int myy = (height + 8 * v0 - 1) / (8 * v0);
if (img1 == null && img3 == null) if (img1 == null && img3 == null)
{
makeImg(mxx, myy); makeImg(mxx, myy);
}
if (progressive) if (progressive)
{ {
@ -1258,6 +1297,7 @@ namespace ImageProcessorCore.Formats.Jpg
// b is the decoded coefficients, in natural (not zig-zag) order. // b is the decoded coefficients, in natural (not zig-zag) order.
Block b = new Block(); Block b = new Block();
int[] dc = new int[maxComponents]; int[] dc = new int[maxComponents];
// bx and by are the location of the current block, in units of 8x8 // bx and by are the location of the current block, in units of 8x8
// blocks: the third block in the first row has (bx, by) = (2, 0). // blocks: the third block in the first row has (bx, by) = (2, 0).
int bx, by, blockCount = 0; int bx, by, blockCount = 0;
@ -1369,8 +1409,7 @@ namespace ImageProcessorCore.Formats.Jpg
eobRun = (ushort)(1 << val0); eobRun = (ushort)(1 << val0);
if (val0 != 0) if (val0 != 0)
{ {
uint bits = decodeBits(val0); eobRun |= (ushort)decodeBits(val0);
eobRun |= (ushort)(bits);
} }
eobRun--; eobRun--;
break; break;
@ -1387,6 +1426,7 @@ namespace ImageProcessorCore.Formats.Jpg
{ {
// We haven't completely decoded this 8x8 block. Save the coefficients. // We haven't completely decoded this 8x8 block. Save the coefficients.
progCoeffs[compIndex][by * mxx * hi + bx] = b; progCoeffs[compIndex][by * mxx * hi + bx] = b;
// At this point, we could execute the rest of the loop body to dequantize and // At this point, we could execute the rest of the loop body to dequantize and
// perform the inverse DCT, to save early stages of a progressive image to the // perform the inverse DCT, to save early stages of a progressive image to the
// *image.YCbCr buffers (the whole point of progressive encoding), but in Go, // *image.YCbCr buffers (the whole point of progressive encoding), but in Go,
@ -1400,7 +1440,7 @@ namespace ImageProcessorCore.Formats.Jpg
for (int zig = 0; zig < Block.blockSize; zig++) for (int zig = 0; zig < Block.blockSize; zig++)
b[unzig[zig]] *= qt[zig]; b[unzig[zig]] *= qt[zig];
IDCT(b); IDCT.Transform(b);
byte[] dst = null; byte[] dst = null;
int offset = 0; int offset = 0;

84
src/ImageProcessorCore/Formats/Jpg/Encoder.cs

@ -1,4 +1,4 @@
namespace ImageProcessorCore.Formats.Jpg namespace ImageProcessorCore.Formats
{ {
using System; using System;
using System.IO; using System.IO;
@ -108,7 +108,7 @@ namespace ImageProcessorCore.Formats.Jpg
public huffmanSpec(byte[] c, byte[] v) { count = c; values = v; } public huffmanSpec(byte[] c, byte[] v) { count = c; values = v; }
public byte[] count; public byte[] count;
public byte[] values; public byte[] values;
}; }
// theHuffmanSpec is the Huffman encoding specifications. // theHuffmanSpec is the Huffman encoding specifications.
// This encoder uses the same Huffman encoding for all images. // This encoder uses the same Huffman encoding for all images.
@ -116,11 +116,11 @@ namespace ImageProcessorCore.Formats.Jpg
// Luminance DC. // Luminance DC.
new huffmanSpec( new huffmanSpec(
new byte[] { 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, new byte[] { 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 },
new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }),
),
new huffmanSpec( new huffmanSpec(
new byte[] { 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 }, new byte[] { 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 },
new byte[] { new byte[]
{
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
@ -141,17 +141,16 @@ namespace ImageProcessorCore.Formats.Jpg
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa, 0xf9, 0xfa}),
}
),
new huffmanSpec( new huffmanSpec(
new byte[] { 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, new byte[] { 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 },
new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }),
),
// Chrominance AC. // Chrominance AC.
new huffmanSpec( new huffmanSpec(
new byte[] { 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 }, new byte[] { 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 },
new byte[] { new byte[]
{
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
@ -173,8 +172,7 @@ namespace ImageProcessorCore.Formats.Jpg
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa, 0xf9, 0xfa,
} })
),
}; };
// huffmanLUT is a compiled look-up table representation of a huffmanSpec. // huffmanLUT is a compiled look-up table representation of a huffmanSpec.
@ -277,9 +275,8 @@ namespace ImageProcessorCore.Formats.Jpg
else else
nBits = 8 + (uint)bitCount[a >> 8]; nBits = 8 + (uint)bitCount[a >> 8];
emitHuff(h, (int)((runLength<<4)|nBits)); emitHuff(h, (int)((uint)(runLength << 4) | nBits));
if (nBits > 0) if (nBits > 0) emit((uint)b & (uint)((1 << ((int)nBits)) - 1), nBits);
emit((uint)b & (uint)((1 << ((int)nBits)) - 1), nBits);
} }
// writeMarkerHeader writes the header for a marker with the given length. // writeMarkerHeader writes the header for a marker with the given length.
@ -352,7 +349,9 @@ namespace ImageProcessorCore.Formats.Jpg
} }
foreach (var s in specs) foreach (var s in specs)
{
markerlen += 1 + 16 + s.values.Length; markerlen += 1 + 16 + s.values.Length;
}
writeMarkerHeader(dhtMarker, markerlen); writeMarkerHeader(dhtMarker, markerlen);
for (int i = 0; i < specs.Length; i++) for (int i = 0; i < specs.Length; i++)
@ -370,7 +369,7 @@ namespace ImageProcessorCore.Formats.Jpg
// natural (not zig-zag) order. // natural (not zig-zag) order.
private int writeBlock(Block b, quantIndex q, int prevDC) private int writeBlock(Block b, quantIndex q, int prevDC)
{ {
FDCT(b); FDCT.Transform(b);
// Emit the DC delta. // Emit the DC delta.
int dc = div(b[0], 8 * quant[(int)q][0]); int dc = div(b[0], 8 * quant[(int)q][0]);
@ -415,55 +414,14 @@ namespace ImageProcessorCore.Formats.Jpg
{ {
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
{ {
byte yy, cb, cr; YCbCr color = m[Math.Min(x + i, xmax), Math.Min(y + j, ymax)];
int index = (8 * j) + i;
var c = m[Math.Min(x+i, xmax), Math.Min(y+j, ymax)]; yBlock[index] = (int)color.Y;
Colors.RGBToYCbCr((byte)(c.R*255), (byte)(c.G*255), (byte)(c.B*255), out yy, out cb, out cr); cbBlock[index] = (int)color.Cb;
yBlock[8*j+i] = yy; crBlock[index] = (int)color.Cr;
cbBlock[8*j+i] = cb;
crBlock[8*j+i] = cr;
}
}
}
// grayToY stores the 8x8 region of m whose top-left corner is p in yBlock.
/*func grayToY(m *image.Gray, p image.Point, yBlock *block) {
b := m.Bounds()
xmax := b.Max.X - 1
ymax := b.Max.Y - 1
pix := m.Pix
for j := 0; j < 8; j++ {
for i := 0; i < 8; i++ {
idx := m.PixOffset(min(p.X+i, xmax), min(p.Y+j, ymax))
yBlock[8*j+i] = int32(pix[idx])
}
}
}
// rgbaToYCbCr is a specialized version of toYCbCr for image.RGBA images.
func rgbaToYCbCr(m *image.RGBA, p image.Point, yBlock, cbBlock, crBlock *block) {
b := m.Bounds()
xmax := b.Max.X - 1
ymax := b.Max.Y - 1
for j := 0; j < 8; j++ {
sj := p.Y + j
if sj > ymax {
sj = ymax
}
offset := (sj-b.Min.Y)*m.Stride - b.Min.X*4
for i := 0; i < 8; i++ {
sx := p.X + i
if sx > xmax {
sx = xmax
} }
pix := m.Pix[offset+sx*4:]
yy, cb, cr := color.RGBToYCbCr(pix[0], pix[1], pix[2])
yBlock[8*j+i] = int32(yy)
cbBlock[8*j+i] = int32(cb)
crBlock[8*j+i] = int32(cr)
} }
} }
}*/
// scale scales the 16x16 region represented by the 4 src blocks to the 8x8 // scale scales the 16x16 region represented by the 4 src blocks to the 8x8
// dst block. // dst block.

75
src/ImageProcessorCore/Formats/Jpg/FDCT.cs

@ -1,9 +1,6 @@
namespace ImageProcessorCore.Formats.Jpg namespace ImageProcessorCore.Formats
{ {
using System; internal class FDCT
using System.IO;
internal partial class Encoder
{ {
// Trigonometric constants in 13-bit fixed point format. // Trigonometric constants in 13-bit fixed point format.
private const int fix_0_298631336 = 2446; private const int fix_0_298631336 = 2446;
@ -24,19 +21,21 @@ namespace ImageProcessorCore.Formats.Jpg
// fdct performs a forward DCT on an 8x8 block of coefficients, including a // fdct performs a forward DCT on an 8x8 block of coefficients, including a
// level shift. // level shift.
private static void FDCT(Block b) public static void Transform(Block b)
{ {
// Pass 1: process rows. // Pass 1: process rows.
for (int y = 0; y < 8; y++) for (int y = 0; y < 8; y++)
{ {
int x0 = b[y*8+0]; int y8 = y * 8;
int x1 = b[y*8+1];
int x2 = b[y*8+2]; int x0 = b[y8 + 0];
int x3 = b[y*8+3]; int x1 = b[y8 + 1];
int x4 = b[y*8+4]; int x2 = b[y8 + 2];
int x5 = b[y*8+5]; int x3 = b[y8 + 3];
int x6 = b[y*8+6]; int x4 = b[y8 + 4];
int x7 = b[y*8+7]; int x5 = b[y8 + 5];
int x6 = b[y8 + 6];
int x7 = b[y8 + 7];
int tmp0 = x0 + x7; int tmp0 = x0 + x7;
int tmp1 = x1 + x6; int tmp1 = x1 + x6;
@ -53,12 +52,12 @@ namespace ImageProcessorCore.Formats.Jpg
tmp2 = x2 - x5; tmp2 = x2 - x5;
tmp3 = x3 - x4; tmp3 = x3 - x4;
b[y*8+0] = (tmp10 + tmp11 - 8*centerJSample) << pass1Bits; b[y8] = (tmp10 + tmp11 - 8 * centerJSample) << pass1Bits;
b[y*8+4] = (tmp10 - tmp11) << pass1Bits; b[y8 + 4] = (tmp10 - tmp11) << pass1Bits;
int z1 = (tmp12 + tmp13) * fix_0_541196100; int z1 = (tmp12 + tmp13) * fix_0_541196100;
z1 += 1 << (constBits - pass1Bits - 1); z1 += 1 << (constBits - pass1Bits - 1);
b[y*8+2] = (z1 + tmp12*fix_0_765366865) >> (constBits - pass1Bits); b[y8 + 2] = (z1 + tmp12 * fix_0_765366865) >> (constBits - pass1Bits);
b[y*8+6] = (z1 - tmp13*fix_1_847759065) >> (constBits - pass1Bits); b[y8 + 6] = (z1 - tmp13 * fix_1_847759065) >> (constBits - pass1Bits);
tmp10 = tmp0 + tmp3; tmp10 = tmp0 + tmp3;
tmp11 = tmp1 + tmp2; tmp11 = tmp1 + tmp2;
@ -77,38 +76,38 @@ namespace ImageProcessorCore.Formats.Jpg
tmp12 += z1; tmp12 += z1;
tmp13 += z1; tmp13 += z1;
b[y*8+1] = (tmp0 + tmp10 + tmp12) >> (constBits - pass1Bits); b[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (constBits - pass1Bits);
b[y*8+3] = (tmp1 + tmp11 + tmp13) >> (constBits - pass1Bits); b[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (constBits - pass1Bits);
b[y*8+5] = (tmp2 + tmp11 + tmp12) >> (constBits - pass1Bits); b[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (constBits - pass1Bits);
b[y*8+7] = (tmp3 + tmp10 + tmp13) >> (constBits - pass1Bits); b[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (constBits - pass1Bits);
} }
// Pass 2: process columns. // Pass 2: process columns.
// We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8.
for (int x = 0; x < 8; x++) for (int x = 0; x < 8; x++)
{ {
int tmp0 = b[0*8+x] + b[7*8+x]; int tmp0 = b[x] + b[56 + x];
int tmp1 = b[1*8+x] + b[6*8+x]; int tmp1 = b[8 + x] + b[48 + x];
int tmp2 = b[2*8+x] + b[5*8+x]; int tmp2 = b[16 + x] + b[40 + x];
int tmp3 = b[3*8+x] + b[4*8+x]; int tmp3 = b[24 + x] + b[32 + x];
int tmp10 = tmp0 + tmp3 + (1 << (pass1Bits - 1)); int tmp10 = tmp0 + tmp3 + (1 << (pass1Bits - 1));
int tmp12 = tmp0 - tmp3; int tmp12 = tmp0 - tmp3;
int tmp11 = tmp1 + tmp2; int tmp11 = tmp1 + tmp2;
int tmp13 = tmp1 - tmp2; int tmp13 = tmp1 - tmp2;
tmp0 = b[0*8+x] - b[7*8+x]; tmp0 = b[x] - b[56 + x];
tmp1 = b[1*8+x] - b[6*8+x]; tmp1 = b[8 + x] - b[48 + x];
tmp2 = b[2*8+x] - b[5*8+x]; tmp2 = b[16 + x] - b[40 + x];
tmp3 = b[3*8+x] - b[4*8+x]; tmp3 = b[24 + x] - b[32 + x];
b[0*8+x] = (tmp10 + tmp11) >> pass1Bits; b[x] = (tmp10 + tmp11) >> pass1Bits;
b[4*8+x] = (tmp10 - tmp11) >> pass1Bits; b[32 + x] = (tmp10 - tmp11) >> pass1Bits;
int z1 = (tmp12 + tmp13) * fix_0_541196100; int z1 = (tmp12 + tmp13) * fix_0_541196100;
z1 += 1 << (constBits + pass1Bits - 1); z1 += 1 << (constBits + pass1Bits - 1);
b[2*8+x] = (z1 + tmp12*fix_0_765366865) >> (constBits + pass1Bits); b[16 + x] = (z1 + tmp12 * fix_0_765366865) >> (constBits + pass1Bits);
b[6*8+x] = (z1 - tmp13*fix_1_847759065) >> (constBits + pass1Bits); b[48 + x] = (z1 - tmp13 * fix_1_847759065) >> (constBits + pass1Bits);
tmp10 = tmp0 + tmp3; tmp10 = tmp0 + tmp3;
tmp11 = tmp1 + tmp2; tmp11 = tmp1 + tmp2;
@ -127,10 +126,10 @@ namespace ImageProcessorCore.Formats.Jpg
tmp12 += z1; tmp12 += z1;
tmp13 += z1; tmp13 += z1;
b[1*8+x] = (tmp0 + tmp10 + tmp12) >> (constBits + pass1Bits); b[8 + x] = (tmp0 + tmp10 + tmp12) >> (constBits + pass1Bits);
b[3*8+x] = (tmp1 + tmp11 + tmp13) >> (constBits + pass1Bits); b[24 + x] = (tmp1 + tmp11 + tmp13) >> (constBits + pass1Bits);
b[5*8+x] = (tmp2 + tmp11 + tmp12) >> (constBits + pass1Bits); b[40 + x] = (tmp2 + tmp11 + tmp12) >> (constBits + pass1Bits);
b[7*8+x] = (tmp3 + tmp10 + tmp13) >> (constBits + pass1Bits); b[56 + x] = (tmp3 + tmp10 + tmp13) >> (constBits + pass1Bits);
} }
} }
} }

9
src/ImageProcessorCore/Formats/Jpg/IDCT.cs

@ -1,9 +1,6 @@
namespace ImageProcessorCore.Formats.Jpg namespace ImageProcessorCore.Formats
{ {
using System; internal class IDCT
using System.IO;
internal partial class Decoder
{ {
private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16)
private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16)
@ -31,7 +28,7 @@ namespace ImageProcessorCore.Formats.Jpg
// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the // For more on the actual algorithm, see Z. Wang, "Fast algorithms for the
// discrete W transform and for the discrete Fourier transform", IEEE Trans. on // discrete W transform and for the discrete Fourier transform", IEEE Trans. on
// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. // ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984.
private static void IDCT(Block src) public static void Transform(Block src)
{ {
// Horizontal 1-D IDCT. // Horizontal 1-D IDCT.
for (int y = 0; y < 8; y++) for (int y = 0; y < 8; y++)

9
src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs

@ -8,7 +8,7 @@ namespace ImageProcessorCore.Formats
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using ImageProcessorCore.Formats.Jpg; using ImageProcessorCore.Formats;
/// <summary> /// <summary>
/// Image decoder for generating an image out of a jpg stream. /// Image decoder for generating an image out of a jpg stream.
@ -133,10 +133,11 @@ namespace ImageProcessorCore.Formats
for (int x = 0; x < pixelWidth; x++) for (int x = 0; x < pixelWidth; x++)
{ {
int offset = ((y * pixelWidth) + x) * 4; int offset = ((y * pixelWidth) + x) * 4;
int sourceOffset = yoff + (3 * x);
pixels[offset + 0] = decoder.imgrgb.pixels[yoff+3*x+0] / 255f; pixels[offset + 0] = decoder.imgrgb.pixels[sourceOffset];
pixels[offset + 1] = decoder.imgrgb.pixels[yoff+3*x+1] / 255f; pixels[offset + 1] = decoder.imgrgb.pixels[sourceOffset + 1];
pixels[offset + 2] = decoder.imgrgb.pixels[yoff+3*x+2] / 255f; pixels[offset + 2] = decoder.imgrgb.pixels[sourceOffset + 2];
pixels[offset + 3] = 1; pixels[offset + 3] = 1;
} }
}); });

6
src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs

@ -7,8 +7,6 @@ namespace ImageProcessorCore.Formats
{ {
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks;
using ImageProcessorCore.Formats.Jpg;
/// <summary> /// <summary>
/// Encoder for writing the data image to a stream in jpeg format. /// Encoder for writing the data image to a stream in jpeg format.
@ -16,7 +14,7 @@ namespace ImageProcessorCore.Formats
public class JpegEncoder : IImageEncoder public class JpegEncoder : IImageEncoder
{ {
/// <summary> /// <summary>
/// The quality. /// The quality used to encode the image.
/// </summary> /// </summary>
private int quality = 75; private int quality = 75;
@ -58,7 +56,7 @@ namespace ImageProcessorCore.Formats
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
var encode = new Encoder(); Encoder encode = new Encoder();
encode.Encode(stream, image, this.Quality); encode.Encode(stream, image, this.Quality);
} }
} }

3
src/ImageProcessorCore/Formats/Jpg/README.md

@ -0,0 +1,3 @@
Encoder/Decoder adapted and extended from:
https://golang.org/src/image/jpeg/
Loading…
Cancel
Save