/// <summary> /// Initializes <see cref="SpectralBlocks"/> /// </summary> /// <param name="memoryManager">The <see cref="MemoryManager"/> to use for buffer allocations.</param> /// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param> public void InitializeDerivedData(MemoryManager memoryManager, GolangJpegDecoderCore decoder) { // For 4-component images (either CMYK or YCbCrK), we only support two // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. // Theoretically, 4-component JPEG images could mix and match hv values // but in practice, those two combinations are the only ones in use, // and it simplifies the applyBlack code below if we can assume that: // - for CMYK, the C and K channels have full samples, and if the M // and Y channels subsample, they subsample both horizontally and // vertically. // - for YCbCrK, the Y and K channels have full samples. this.SizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(this.SamplingFactors); if (this.Index == 0 || this.Index == 3) { this.SubSamplingDivisors = new Size(1, 1); } else { GolangComponent c0 = decoder.Components[0]; this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); } this.SpectralBlocks = memoryManager.Allocate2D <Block8x8>(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true); }
public static SpectralData LoadFromImageSharpDecoder(GolangJpegDecoderCore decoder) { GolangComponent[] srcComponents = decoder.Components; LibJpegTools.ComponentData[] destComponents = srcComponents.Select(LibJpegTools.ComponentData.Load).ToArray(); return(new SpectralData(destComponents)); }
/// <summary> /// Read Huffman data from Jpeg scans in <see cref="GolangJpegDecoderCore.InputStream"/>, /// and decode it as <see cref="Block8x8"/> into <see cref="GolangComponent.SpectralBlocks"/>. /// /// The blocks are traversed one MCU at a time. For 4:2:0 chroma /// subsampling, there are four Y 8x8 blocks in every 16x16 MCU. /// For a baseline 32x16 pixel image, the Y blocks visiting order is: /// 0 1 4 5 /// 2 3 6 7 /// For progressive images, the interleaved scans (those with component count > 1) /// are traversed as above, but non-interleaved scans are traversed left /// to right, top to bottom: /// 0 1 2 3 /// 4 5 6 7 /// Only DC scans (zigStart == 0) can be interleave AC scans must have /// only one component. /// To further complicate matters, for non-interleaved scans, there is no /// data for any blocks that are inside the image at the MCU level but /// outside the image at the pixel level. For example, a 24x16 pixel 4:2:0 /// progressive image consists of two 16x16 MCUs. The interleaved scans /// will process 8 Y blocks: /// 0 1 4 5 /// 2 3 6 7 /// The non-interleaved scans will process only 6 Y blocks: /// 0 1 2 /// 3 4 5 /// </summary> /// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param> public void DecodeBlocks(GolangJpegDecoderCore decoder) { decoder.InputProcessor.ResetErrorState(); this.blockCounter = 0; this.mcuCounter = 0; this.expectedRst = JpegConstants.Markers.RST0; for (int my = 0; my < decoder.MCUCountY; my++) { for (int mx = 0; mx < decoder.MCUCountX; mx++) { this.DecodeBlocksAtMcuIndex(decoder, mx, my); this.mcuCounter++; // Handling restart intervals // Useful info: https://stackoverflow.com/a/8751802 if (decoder.IsAtRestartInterval(this.mcuCounter)) { this.ProcessRSTMarker(decoder); this.Reset(decoder); } } } }
public void ColorSpace_IsDeducedCorrectlyGolang(string imageFile, object expectedColorSpaceValue) { var expecteColorSpace = (JpegColorSpace)expectedColorSpaceValue; using (GolangJpegDecoderCore decoder = JpegFixture.ParseGolangStream(imageFile)) { Assert.Equal(expecteColorSpace, decoder.ColorSpace); } }
internal static GolangJpegDecoderCore ParseGolangStream(string testFileName, bool metaDataOnly = false) { byte[] bytes = TestFile.Create(testFileName).Bytes; using (var ms = new MemoryStream(bytes)) { var decoder = new GolangJpegDecoderCore(Configuration.Default, new JpegDecoder()); decoder.ParseStream(ms, metaDataOnly); return(decoder); } }
public void OriginalDecoder_ParseStream_SaveSpectralResult <TPixel>(TestImageProvider <TPixel> provider) where TPixel : struct, IPixel <TPixel> { var decoder = new GolangJpegDecoderCore(Configuration.Default, new JpegDecoder()); byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; using (var ms = new MemoryStream(sourceBytes)) { decoder.ParseStream(ms, false); var data = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); VerifyJpeg.SaveSpectralImage(provider, data); } }
public void ComponentScalingIsCorrect_1ChannelJpegGolang() { using (GolangJpegDecoderCore decoder = JpegFixture.ParseGolangStream(TestImages.Jpeg.Baseline.Jpeg400)) { Assert.Equal(1, decoder.ComponentCount); Assert.Equal(1, decoder.Components.Length); Size expectedSizeInBlocks = decoder.ImageSizeInPixels.DivideRoundUp(8); Assert.Equal(expectedSizeInBlocks, decoder.ImageSizeInMCU); var uniform1 = new Size(1, 1); GolangComponent c0 = decoder.Components[0]; VerifyJpeg.VerifyComponent(c0, expectedSizeInBlocks, uniform1, uniform1); } }
public void PrintComponentDataGolang(string imageFile) { var sb = new StringBuilder(); using (GolangJpegDecoderCore decoder = JpegFixture.ParseGolangStream(imageFile)) { sb.AppendLine(imageFile); sb.AppendLine($"Size:{decoder.ImageSizeInPixels} MCU:{decoder.ImageSizeInMCU}"); GolangComponent c0 = decoder.Components[0]; GolangComponent c1 = decoder.Components[1]; sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}"); sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}"); } this.Output.WriteLine(sb.ToString()); }
private void DecodeBlocksAtMcuIndex(GolangJpegDecoderCore decoder, int mx, int my) { for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++) { this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex; GolangComponent component = decoder.Components[this.ComponentIndex]; this.hi = component.HorizontalSamplingFactor; int vi = component.VerticalSamplingFactor; for (int j = 0; j < this.hi * vi; j++) { if (this.componentScanCount != 1) { this.bx = (this.hi * mx) + (j % this.hi); this.by = (vi * my) + (j / this.hi); } else { int q = decoder.MCUCountX * this.hi; this.bx = this.blockCounter % q; this.by = this.blockCounter / q; this.blockCounter++; if (this.bx * 8 >= decoder.ImageWidth || this.by * 8 >= decoder.ImageHeight) { continue; } } // Find the block at (bx,by) in the component's buffer: ref Block8x8 blockRefOnHeap = ref component.GetBlockReference(this.bx, this.by); // Copy block to stack this.data.Block = blockRefOnHeap; if (!decoder.InputProcessor.ReachedEOF) { this.DecodeBlock(decoder, scanIndex); } // Store the result block: blockRefOnHeap = this.data.Block; } }
public void VerifySpectralCorrectness_Golang <TPixel>(TestImageProvider <TPixel> provider) where TPixel : struct, IPixel <TPixel> { if (!TestEnvironment.IsWindows) { return; } var decoder = new GolangJpegDecoderCore(Configuration.Default, new GolangJpegDecoder()); byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; using (var ms = new MemoryStream(sourceBytes)) { decoder.ParseStream(ms); var imageSharpData = LibJpegTools.SpectralData.LoadFromImageSharpDecoder(decoder); this.VerifySpectralCorrectness <TPixel>(provider, imageSharpData); } }
public void ComponentScalingIsCorrect_MultiChannelJpegGolang( string imageFile, int componentCount, object expectedLumaFactors, object expectedChromaFactors) { var fLuma = (Size)expectedLumaFactors; var fChroma = (Size)expectedChromaFactors; using (GolangJpegDecoderCore decoder = JpegFixture.ParseGolangStream(imageFile)) { Assert.Equal(componentCount, decoder.ComponentCount); Assert.Equal(componentCount, decoder.Components.Length); GolangComponent c0 = decoder.Components[0]; GolangComponent c1 = decoder.Components[1]; GolangComponent c2 = decoder.Components[2]; var uniform1 = new Size(1, 1); Size expectedLumaSizeInBlocks = decoder.ImageSizeInMCU.MultiplyBy(fLuma); Size divisor = fLuma.DivideBy(fChroma); Size expectedChromaSizeInBlocks = expectedLumaSizeInBlocks.DivideRoundUp(divisor); VerifyJpeg.VerifyComponent(c0, expectedLumaSizeInBlocks, fLuma, uniform1); VerifyJpeg.VerifyComponent(c1, expectedChromaSizeInBlocks, fChroma, divisor); VerifyJpeg.VerifyComponent(c2, expectedChromaSizeInBlocks, fChroma, divisor); if (componentCount == 4) { GolangComponent c3 = decoder.Components[2]; VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, fLuma, uniform1); } } }
/// <summary> /// Initializes all component data except <see cref="SpectralBlocks"/>. /// </summary> /// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param> public void InitializeCoreData(GolangJpegDecoderCore decoder) { // Section B.2.2 states that "the value of C_i shall be different from // the values of C_1 through C_(i-1)". int i = this.Index; for (int j = 0; j < this.Index; j++) { if (this.Identifier == decoder.Components[j].Identifier) { throw new ImageFormatException("Repeated component identifier"); } } this.QuantizationTableIndex = decoder.Temp[8 + (3 * i)]; if (this.QuantizationTableIndex > GolangJpegDecoderCore.MaxTq) { throw new ImageFormatException("Bad Tq value"); } byte hv = decoder.Temp[7 + (3 * i)]; int h = hv >> 4; int v = hv & 0x0f; if (h < 1 || h > 4 || v < 1 || v > 4) { throw new ImageFormatException("Unsupported Luma/chroma subsampling ratio"); } if (h == 3 || v == 3) { throw new ImageFormatException("Lnsupported subsampling ratio"); } switch (decoder.ComponentCount) { case 1: // If a JPEG image has only one component, section A.2 says "this data // is non-interleaved by definition" and section A.2.2 says "[in this // case...] the order of data units within a scan shall be left-to-right // and top-to-bottom... regardless of the values of H_1 and V_1". Section // 4.8.2 also says "[for non-interleaved data], the MCU is defined to be // one data unit". Similarly, section A.1.1 explains that it is the ratio // of H_i to max_j(H_j) that matters, and similarly for V. For grayscale // images, H_1 is the maximum H_j for all components j, so that ratio is // always 1. The component's (h, v) is effectively always (1, 1): even if // the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8 // MCUs, not two 16x8 MCUs. h = 1; v = 1; break; case 3: // For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0, // 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the // (h, v) values for the Y component are either (1, 1), (1, 2), // (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values // must be a multiple of the Cb and Cr component's values. We also // assume that the two chroma components have the same subsampling // ratio. switch (i) { case 0: { // Y. // We have already verified, above, that h and v are both // either 1, 2 or 4, so invalid (h, v) combinations are those // with v == 4. if (v == 4) { throw new ImageFormatException("Unsupported subsampling ratio"); } break; } case 1: { // Cb. Size s0 = decoder.Components[0].SamplingFactors; if (s0.Width % h != 0 || s0.Height % v != 0) { throw new ImageFormatException("Unsupported subsampling ratio"); } break; } case 2: { // Cr. Size s1 = decoder.Components[1].SamplingFactors; if (s1.Width != h || s1.Height != v) { throw new ImageFormatException("Unsupported subsampling ratio"); } break; } } break; case 4: // For 4-component images (either CMYK or YCbCrK), we only support two // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. // Theoretically, 4-component JPEG images could mix and match hv values // but in practice, those two combinations are the only ones in use, // and it simplifies the applyBlack code below if we can assume that: // - for CMYK, the C and K channels have full samples, and if the M // and Y channels subsample, they subsample both horizontally and // vertically. // - for YCbCrK, the Y and K channels have full samples. switch (i) { case 0: if (hv != 0x11 && hv != 0x22) { throw new ImageFormatException("Unsupported subsampling ratio"); } break; case 1: case 2: if (hv != 0x11) { throw new ImageFormatException("Unsupported subsampling ratio"); } break; case 3: Size s0 = decoder.Components[0].SamplingFactors; if (s0.Width != h || s0.Height != v) { throw new ImageFormatException("Unsupported subsampling ratio"); } break; } break; } this.SamplingFactors = new Size(h, v); }
/// <summary> /// Initializes a default-constructed <see cref="GolangJpegScanDecoder"/> instance for reading data from <see cref="GolangJpegDecoderCore"/>-s stream. /// </summary> /// <param name="p">Pointer to <see cref="GolangJpegScanDecoder"/> on the stack</param> /// <param name="decoder">The <see cref="GolangJpegDecoderCore"/> instance</param> /// <param name="remaining">The remaining bytes in the segment block.</param> public static void InitStreamReading(GolangJpegScanDecoder *p, GolangJpegDecoderCore decoder, int remaining) { p->data = ComputationData.Create(); p->pointers = new DataPointers(&p->data); p->InitStreamReadingImpl(decoder, remaining); }