Browse Source

At least render something when progressive.

This allows parsing of the broken eof images. The non-progressive
renders perfectly, the progressive appears to not render the final
scan.... or something.
pull/85/head
James Jackson-South 9 years ago
parent
commit
d05b06bbbd
  1. 33
      src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs
  2. 284
      src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs
  3. 6
      tests/ImageSharp.Tests/FileTestBase.cs
  4. 1
      tests/ImageSharp.Tests/TestImages.cs
  5. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Jpg/badeofprog.jpg

33
src/ImageSharp.Formats.Jpeg/Components/Decoder/JpegScanDecoder.cs

@ -356,11 +356,21 @@ namespace ImageSharp.Formats.Jpg
throw new ImageFormatException("Excessive DC component"); throw new ImageFormatException("Excessive DC component");
} }
int deltaDC = decoder.Bits.ReceiveExtend(value, decoder); // TODO: Handle the EOFException and piece together the final progressive scan.
this.pointers.Dc[compIndex] += deltaDC; try
{
int deltaDC = decoder.Bits.ReceiveExtend(value, decoder);
this.pointers.Dc[compIndex] += deltaDC;
// b[0] = dc[compIndex] << al; // b[0] = dc[compIndex] << al;
Block8x8F.SetScalarAt(b, 0, this.pointers.Dc[compIndex] << this.al); Block8x8F.SetScalarAt(b, 0, this.pointers.Dc[compIndex] << this.al);
}
catch (JpegDecoderCore.EOFException)
{
// Do something clever here. I'm just undoing ReceiveExtend
decoder.Bits.UnreadBits += value;
decoder.Bits.Mask >>= value;
}
} }
if (zig <= this.zigEnd && this.eobRun > 0) if (zig <= this.zigEnd && this.eobRun > 0)
@ -383,7 +393,20 @@ namespace ImageSharp.Formats.Jpg
break; break;
} }
int ac = decoder.Bits.ReceiveExtend(val1, decoder); int ac; // = decoder.Bits.ReceiveExtend(val1, decoder);
// TODO: Handle the EOFException and piece together the final progressive scan.
try
{
ac = decoder.Bits.ReceiveExtend(val1, decoder);
}
catch (JpegDecoderCore.EOFException)
{
// Do something clever here. I'm just undoing ReceiveExtend
decoder.Bits.UnreadBits += val1;
decoder.Bits.Mask >>= val1;
break;
}
// b[Unzig[zig]] = ac << al; // b[Unzig[zig]] = ac << al;
Block8x8F.SetScalarAt(b, this.pointers.Unzig[zig], ac << this.al); Block8x8F.SetScalarAt(b, this.pointers.Unzig[zig], ac << this.al);

284
src/ImageSharp.Formats.Jpeg/JpegDecoderCore.cs

@ -186,164 +186,162 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Missing SOI marker."); throw new ImageFormatException("Missing SOI marker.");
} }
// Process the remaining segments until the End Of Image marker. while (true)
bool processBytes = true;
// we can't currently short circute progressive images so don't try.
while (processBytes)
{ {
this.ReadFull(this.Temp, 0, 2); try
while (this.Temp[0] != 0xff)
{ {
// Strictly speaking, this is a format error. However, libjpeg is this.ReadFull(this.Temp, 0, 2);
// liberal in what it accepts. As of version 9, next_marker in while (this.Temp[0] != 0xff)
// jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and {
// continues to decode the stream. Even before next_marker sees // Strictly speaking, this is a format error. However, libjpeg is
// extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many // liberal in what it accepts. As of version 9, next_marker in
// bytes as it can, possibly past the end of a scan's data. It // jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and
// effectively puts back any markers that it overscanned (e.g. an // continues to decode the stream. Even before next_marker sees
// "\xff\xd9" EOI marker), but it does not put back non-marker data, // extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many
// and thus it can silently ignore a small number of extraneous // bytes as it can, possibly past the end of a scan's data. It
// non-marker bytes before next_marker has a chance to see them (and // effectively puts back any markers that it overscanned (e.g. an
// print a warning). // "\xff\xd9" EOI marker), but it does not put back non-marker data,
// We are therefore also liberal in what we accept. Extraneous data // and thus it can silently ignore a small number of extraneous
// is silently ignore // non-marker bytes before next_marker has a chance to see them (and
// This is similar to, but not exactly the same as, the restart // print a warning).
// mechanism within a scan (the RST[0-7] markers). // We are therefore also liberal in what we accept. Extraneous data
// Note that extraneous 0xff bytes in e.g. SOS data are escaped as // is silently ignore
// "\xff\x00", and so are detected a little further down below. // This is similar to, but not exactly the same as, the restart
this.Temp[0] = this.Temp[1]; // mechanism within a scan (the RST[0-7] markers).
this.Temp[1] = this.ReadByte(); // Note that extraneous 0xff bytes in e.g. SOS data are escaped as
} // "\xff\x00", and so are detected a little further down below.
this.Temp[0] = this.Temp[1];
this.Temp[1] = this.ReadByte();
}
byte marker = this.Temp[1]; byte marker = this.Temp[1];
if (marker == 0) if (marker == 0)
{ {
// Treat "\xff\x00" as extraneous data. // Treat "\xff\x00" as extraneous data.
continue; continue;
} }
while (marker == 0xff) while (marker == 0xff)
{ {
// Section B.1.1.2 says, "Any marker may optionally be preceded by any // Section B.1.1.2 says, "Any marker may optionally be preceded by any
// number of fill bytes, which are bytes assigned code X'FF'". // number of fill bytes, which are bytes assigned code X'FF'".
marker = this.ReadByte(); marker = this.ReadByte();
} }
// End Of Image. // End Of Image.
if (marker == JpegConstants.Markers.EOI) if (marker == JpegConstants.Markers.EOI)
{ {
break; break;
} }
if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7) if (marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7)
{ {
// Figures B.2 and B.16 of the specification suggest that restart markers should // Figures B.2 and B.16 of the specification suggest that restart markers should
// only occur between Entropy Coded Segments and not after the final ECS. // only occur between Entropy Coded Segments and not after the final ECS.
// However, some encoders may generate incorrect JPEGs with a final restart // However, some encoders may generate incorrect JPEGs with a final restart
// marker. That restart marker will be seen here instead of inside the ProcessSOS // marker. That restart marker will be seen here instead of inside the ProcessSOS
// method, and is ignored as a harmless error. Restart markers have no extra data, // method, and is ignored as a harmless error. Restart markers have no extra data,
// so we check for this before we read the 16-bit length of the segment. // so we check for this before we read the 16-bit length of the segment.
continue; continue;
} }
// Read the 16-bit length of the segment. The value includes the 2 bytes for the // Read the 16-bit length of the segment. The value includes the 2 bytes for the
// length itself, so we subtract 2 to get the number of remaining bytes. // length itself, so we subtract 2 to get the number of remaining bytes.
this.ReadFull(this.Temp, 0, 2); this.ReadFull(this.Temp, 0, 2);
int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2; int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2;
if (remaining < 0) if (remaining < 0)
{ {
throw new ImageFormatException("Short segment length."); throw new ImageFormatException("Short segment length.");
} }
switch (marker) switch (marker)
{ {
case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF0:
case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2: case JpegConstants.Markers.SOF2:
this.IsProgressive = marker == JpegConstants.Markers.SOF2; this.IsProgressive = marker == JpegConstants.Markers.SOF2;
this.ProcessStartOfFrameMarker(remaining); this.ProcessStartOfFrameMarker(remaining);
if (configOnly && this.isJfif) if (configOnly && this.isJfif)
{ {
return; return;
} }
break; break;
case JpegConstants.Markers.DHT: case JpegConstants.Markers.DHT:
if (configOnly) if (configOnly)
{ {
this.Skip(remaining); this.Skip(remaining);
} }
else else
{ {
this.ProcessDefineHuffmanTablesMarker(remaining); this.ProcessDefineHuffmanTablesMarker(remaining);
} }
break; break;
case JpegConstants.Markers.DQT: case JpegConstants.Markers.DQT:
if (configOnly) if (configOnly)
{ {
this.Skip(remaining); this.Skip(remaining);
} }
else else
{ {
this.ProcessDqt(remaining); this.ProcessDqt(remaining);
} }
break; break;
case JpegConstants.Markers.SOS: case JpegConstants.Markers.SOS:
if (configOnly) if (configOnly)
{ {
return; return;
} }
// when this is a progressive image this gets called a number of times this.ProcessStartOfScan(remaining);
// need to know how many times this should be called in total.
this.ProcessStartOfScan(remaining);
if (!this.IsProgressive)
{
// if this is not a progressive image we can stop processing bytes as we now have the image data.
processBytes = false;
}
break; break;
case JpegConstants.Markers.DRI: case JpegConstants.Markers.DRI:
if (configOnly) if (configOnly)
{ {
this.Skip(remaining); this.Skip(remaining);
} }
else else
{ {
this.ProcessDefineRestartIntervalMarker(remaining); this.ProcessDefineRestartIntervalMarker(remaining);
} }
break; break;
case JpegConstants.Markers.APP0: case JpegConstants.Markers.APP0:
this.ProcessApplicationHeader(remaining); this.ProcessApplicationHeader(remaining);
break; break;
case JpegConstants.Markers.APP1: case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining, image); this.ProcessApp1Marker(remaining, image);
break; break;
case JpegConstants.Markers.APP14: case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining); this.ProcessApp14Marker(remaining);
break; break;
default: default:
if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15) if ((marker >= JpegConstants.Markers.APP0 && marker <= JpegConstants.Markers.APP15)
|| marker == JpegConstants.Markers.COM) || marker == JpegConstants.Markers.COM)
{ {
this.Skip(remaining); this.Skip(remaining);
} }
else if (marker < JpegConstants.Markers.SOF0) else if (marker < JpegConstants.Markers.SOF0)
{ {
// See Table B.1 "Marker code assignments". // See Table B.1 "Marker code assignments".
throw new ImageFormatException("Unknown marker"); throw new ImageFormatException("Unknown marker");
} }
else else
{ {
throw new ImageFormatException("Unknown marker"); throw new ImageFormatException("Unknown marker");
} }
break; break;
}
}
catch (EOFException)
{
// For non-progressive images this is a simple way to handle a missing EOI
// TODO: For progressive we still have to handle the exception within JpegScanDecoder to include last scan
break;
} }
} }

6
tests/ImageSharp.Tests/FileTestBase.cs

@ -22,9 +22,11 @@ namespace ImageSharp.Tests
// TestFile.Create(TestImages.Png.Pd), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Pd), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Floorplan), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Floorplan), // Perf: Enable for local testing only
TestFile.Create(TestImages.Jpeg.Calliphora), TestFile.Create(TestImages.Jpeg.Calliphora),
// TestFile.Create(TestImages.Jpeg.BadEOF), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.BadEOFProgressive),
// TestFile.Create(TestImages.Jpeg.Ycck), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Ycck), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Cmyk), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Cmyk), // Perf: Enable for local testing only
TestFile.Create(TestImages.Jpeg.Turtle), // TestFile.Create(TestImages.Jpeg.Turtle), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Fb), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Fb), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.Progress), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Progress), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Jpeg.GammaDalaiLamaGray), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.GammaDalaiLamaGray), // Perf: Enable for local testing only
@ -33,7 +35,7 @@ namespace ImageSharp.Tests
// TestFile.Create(TestImages.Png.Blur), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Blur), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Indexed), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Indexed), // Perf: Enable for local testing only
TestFile.Create(TestImages.Png.Splash), TestFile.Create(TestImages.Png.Splash),
TestFile.Create(TestImages.Png.Powerpoint), // TestFile.Create(TestImages.Png.Powerpoint), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.SplashInterlaced), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.SplashInterlaced), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Interlaced), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Interlaced), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Png.Filter0), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Filter0), // Perf: Enable for local testing only

1
tests/ImageSharp.Tests/TestImages.cs

@ -52,6 +52,7 @@ namespace ImageSharp.Tests
public const string Hiyamugi = "Jpg/Hiyamugi.jpg"; public const string Hiyamugi = "Jpg/Hiyamugi.jpg";
public const string BadEOF = "Jpg/badeof.jpg"; public const string BadEOF = "Jpg/badeof.jpg";
public const string BadEOFProgressive = "Jpg/badeofprog.jpg";
public const string Snake = "Jpg/Snake.jpg"; public const string Snake = "Jpg/Snake.jpg";
public const string Lake = "Jpg/Lake.jpg"; public const string Lake = "Jpg/Lake.jpg";

BIN
tests/ImageSharp.Tests/TestImages/Formats/Jpg/badeofprog.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Loading…
Cancel
Save