public virtual int Identify(bool loadQuantizationTables) { if (_inputBuffer.IsEmpty) { throw new InvalidOperationException("Input buffer is not specified."); } JpegReader reader = new JpegReader(_inputBuffer); // Reset frame header _frameHeader = default; bool toContinue = true; while (toContinue && !reader.IsEmpty) { // Read next marker if (!reader.TryReadMarker(out JpegMarker marker)) { ThrowInvalidDataException(reader.ConsumedByteCount, "No marker found."); } toContinue = ProcessMarkerForIdentification(marker, ref reader, loadQuantizationTables); } if (_frameHeader is null) { throw new InvalidOperationException("Frame header was not found."); } return(reader.ConsumedByteCount); }
private void ProcessFrameHeader(ref JpegReader reader, bool metadataOnly, bool overrideAllowed) { // Read length field if (!reader.TryReadLength(out ushort length)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment length."); return; } if (!reader.TryReadBytes(length, out ReadOnlySequence <byte> buffer)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment content."); return; } if (!JpegFrameHeader.TryParse(buffer, metadataOnly, out JpegFrameHeader frameHeader, out int bytesConsumed)) { ThrowInvalidDataException(reader.ConsumedByteCount - length + bytesConsumed, "Failed to parse frame header."); return; } if (!overrideAllowed && _frameHeader.HasValue) { ThrowInvalidDataException(reader.ConsumedByteCount, "Multiple frame is not supported."); return; } _frameHeader = frameHeader; }
protected virtual bool ProcessMarkerForIdentification(JpegMarker marker, ref JpegReader reader, bool loadQuantizationTables) { switch (marker) { case JpegMarker.StartOfImage: break; case JpegMarker.StartOfFrame0: case JpegMarker.StartOfFrame1: case JpegMarker.StartOfFrame2: case JpegMarker.StartOfFrame3: case JpegMarker.StartOfFrame9: case JpegMarker.StartOfFrame10: case JpegMarker.StartOfFrame5: case JpegMarker.StartOfFrame6: case JpegMarker.StartOfFrame7: case JpegMarker.StartOfFrame11: case JpegMarker.StartOfFrame13: case JpegMarker.StartOfFrame14: case JpegMarker.StartOfFrame15: StartOfFrame = marker; ProcessFrameHeader(ref reader, false, false); break; case JpegMarker.StartOfScan: ProcessScanHeader(ref reader, true); break; case JpegMarker.DefineRestartInterval: ProcessDefineRestartInterval(ref reader); break; case JpegMarker.DefineQuantizationTable: ProcessDefineQuantizationTable(ref reader, loadQuantizationTables); break; case JpegMarker.DefineRestart0: case JpegMarker.DefineRestart1: case JpegMarker.DefineRestart2: case JpegMarker.DefineRestart3: case JpegMarker.DefineRestart4: case JpegMarker.DefineRestart5: case JpegMarker.DefineRestart6: case JpegMarker.DefineRestart7: break; case JpegMarker.EndOfImage: return(false); default: ProcessOtherMarker(ref reader); break; } return(true); }
private static void SkipMarkerData(ref JpegReader reader) { if (!reader.TryReadLength(out ushort length)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment length."); } if (!reader.TryAdvance(length)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment content."); } }
private void ProcessOtherMarker(ref JpegReader reader) { if (!reader.TryReadLength(out ushort length)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment length."); return; } if (!reader.TryAdvance(length)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data reached."); return; } }
private void ProcessDefineQuantizationTable(ref JpegReader reader) { if (!reader.TryReadLength(out ushort length)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment length."); return; } if (!reader.TryReadBytes(length, out ReadOnlySequence <byte> buffer)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment content."); return; } ProcessDefineQuantizationTable(buffer, reader.ConsumedByteCount - length); }
private static ReadOnlySequence <byte> CopyMarkerData(ref JpegReader reader, ref JpegWriter writer) { if (!reader.TryReadLength(out ushort length)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment length."); } if (!reader.TryReadBytes(length, out ReadOnlySequence <byte> buffer)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment content."); } writer.WriteLength(length); writer.WriteBytes(buffer); return(buffer); }
private JpegScanHeader ProcessScanHeader(ref JpegReader reader, bool metadataOnly) { if (!reader.TryReadLength(out ushort length)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment length."); } if (!reader.TryReadBytes(length, out ReadOnlySequence <byte> buffer)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment content."); } if (!JpegScanHeader.TryParse(buffer, metadataOnly, out JpegScanHeader scanHeader, out int bytesConsumed)) { ThrowInvalidDataException(reader.ConsumedByteCount - length + bytesConsumed, "Failed to parse scan header."); } return(scanHeader); }
private void ProcessDefineRestartInterval(ref JpegReader reader) { if (!reader.TryReadLength(out ushort length)) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment length."); return; } if (!reader.TryReadBytes(length, out ReadOnlySequence <byte> buffer) || buffer.Length < 2) { ThrowInvalidDataException(reader.ConsumedByteCount, "Unexpected end of input data when reading segment content."); return; } Span <byte> local = stackalloc byte[2]; buffer.Slice(0, 2).CopyTo(local); _restartInterval = BinaryPrimitives.ReadUInt16BigEndian(local); }
private void CopyScanBaseline(ref JpegReader reader, ref JpegWriter writer, JpegScanHeader scanHeader) { JpegFrameHeader frameHeader = _frameHeader.GetValueOrDefault(); if (scanHeader.Components is null) { throw new InvalidOperationException(); } // Compute maximum sampling factor byte maxHorizontalSampling = 1; byte maxVerticalSampling = 1; foreach (JpegFrameComponentSpecificationParameters currentFrameComponent in frameHeader.Components !) { maxHorizontalSampling = Math.Max(maxHorizontalSampling, currentFrameComponent.HorizontalSamplingFactor); maxVerticalSampling = Math.Max(maxVerticalSampling, currentFrameComponent.VerticalSamplingFactor); } // Resolve each component JpegTranscodeComponent[] components = new JpegTranscodeComponent[scanHeader.NumberOfComponents]; for (int i = 0; i < scanHeader.NumberOfComponents; i++) { JpegScanComponentSpecificationParameters scanComponenet = scanHeader.Components[i]; int componentIndex = 0; JpegFrameComponentSpecificationParameters?frameComponent = null; for (int j = 0; j < frameHeader.NumberOfComponents; j++) { JpegFrameComponentSpecificationParameters currentFrameComponent = frameHeader.Components[j]; if (scanComponenet.ScanComponentSelector == currentFrameComponent.Identifier) { componentIndex = j; frameComponent = currentFrameComponent; } } if (frameComponent is null) { throw new InvalidDataException(); } components[i] = new JpegTranscodeComponent { ComponentIndex = componentIndex, HorizontalSamplingFactor = frameComponent.GetValueOrDefault().HorizontalSamplingFactor, VerticalSamplingFactor = frameComponent.GetValueOrDefault().VerticalSamplingFactor, DcTable = GetHuffmanTable(true, scanComponenet.DcEntropyCodingTableSelector), AcTable = GetHuffmanTable(false, scanComponenet.AcEntropyCodingTableSelector), DcEncodingTable = _encodingTables.GetTable(true, scanComponenet.DcEntropyCodingTableSelector), AcEncodingTable = _encodingTables.GetTable(false, scanComponenet.AcEntropyCodingTableSelector) }; } // Prepare int mcusPerLine = (frameHeader.SamplesPerLine + 8 * maxHorizontalSampling - 1) / (8 * maxHorizontalSampling); int mcusPerColumn = (frameHeader.NumberOfLines + 8 * maxVerticalSampling - 1) / (8 * maxVerticalSampling); JpegBitReader bitReader = new JpegBitReader(reader.RemainingBytes); int mcusBeforeRestart = _restartInterval; bool eoiReached = false; writer.EnterBitMode(); for (int rowMcu = 0; rowMcu < mcusPerColumn && !eoiReached; rowMcu++) { for (int colMcu = 0; colMcu < mcusPerLine && !eoiReached; colMcu++) { foreach (JpegTranscodeComponent component in components) { int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; for (int y = 0; y < v; y++) { for (int x = 0; x < h; x++) { CopyBlockBaseline(ref bitReader, ref writer, component); } } } if (_restartInterval > 0 && (--mcusBeforeRestart) == 0) { bitReader.AdvanceAlignByte(); JpegMarker marker = bitReader.TryReadMarker(); if (marker == JpegMarker.EndOfImage) { eoiReached = true; break; } if (!marker.IsRestartMarker()) { throw new InvalidOperationException("Expect restart marker."); } mcusBeforeRestart = _restartInterval; writer.ExitBitMode(); writer.WriteMarker(marker); writer.EnterBitMode(); } } } bitReader.AdvanceAlignByte(); writer.ExitBitMode(); int bytesConsumed = reader.RemainingByteCount - bitReader.RemainingBits / 8; if (eoiReached) { bytesConsumed -= 2; } else if (bitReader.TryPeekMarker() != 0) { if (!bitReader.TryPeekMarker().IsRestartMarker()) { bytesConsumed -= 2; } } reader.TryAdvance(bytesConsumed); }
public void Scan() { if (_inputBuffer.IsEmpty) { throw new InvalidOperationException("Input buffer is not specified."); } JpegReader reader = new JpegReader(_inputBuffer); // Reset frame header _frameHeader = default; bool scanRead = false; bool endOfImageReached = false; while (!endOfImageReached && !reader.IsEmpty) { // Read next marker if (!reader.TryReadMarker(out JpegMarker marker)) { ThrowInvalidDataException(reader.ConsumedByteCount, "No marker found."); return; } switch (marker) { case JpegMarker.StartOfImage: break; case JpegMarker.StartOfFrame0: ProcessFrameHeader(ref reader, false, false); break; case JpegMarker.StartOfFrame1: ProcessFrameHeader(ref reader, false, false); break; case JpegMarker.StartOfFrame3: case JpegMarker.StartOfFrame5: case JpegMarker.StartOfFrame6: case JpegMarker.StartOfFrame7: case JpegMarker.StartOfFrame9: case JpegMarker.StartOfFrame10: case JpegMarker.StartOfFrame11: case JpegMarker.StartOfFrame13: case JpegMarker.StartOfFrame14: case JpegMarker.StartOfFrame15: ThrowInvalidDataException(reader.ConsumedByteCount, $"This type of JPEG stream is not supported ({marker})."); return; case JpegMarker.DefineHuffmanTable: ProcessDefineHuffmanTable(ref reader); break; case JpegMarker.DefineQuantizationTable: ProcessDefineQuantizationTable(ref reader); break; case JpegMarker.DefineRestartInterval: ProcessDefineRestartInterval(ref reader); break; case JpegMarker.StartOfScan: JpegScanHeader scanHeader = ProcessScanHeader(ref reader, false); ProcessScanBaseline(ref reader, scanHeader); scanRead = true; break; case JpegMarker.DefineRestart0: case JpegMarker.DefineRestart1: case JpegMarker.DefineRestart2: case JpegMarker.DefineRestart3: case JpegMarker.DefineRestart4: case JpegMarker.DefineRestart5: case JpegMarker.DefineRestart6: case JpegMarker.DefineRestart7: break; case JpegMarker.EndOfImage: endOfImageReached = true; break; default: ProcessOtherMarker(ref reader); break; } } if (!scanRead) { ThrowInvalidDataException(reader.ConsumedByteCount, "No image data is read."); return; } }
public void Optimize(bool strip = true) { if (_encodingTables.IsEmpty) { throw new InvalidOperationException(); } IBufferWriter <byte> bufferWriter = _output ?? throw new InvalidOperationException(); JpegReader reader = new JpegReader(_inputBuffer); var writer = new JpegWriter(bufferWriter, _minimumBufferSegmentSize); bool eoiReached = false; bool huffmanTableWritten = false; bool quantizationTableWritten = false; while (!eoiReached && !reader.IsEmpty) { if (!reader.TryReadMarker(out JpegMarker marker)) { ThrowInvalidDataException(reader.ConsumedByteCount, "No marker found."); return; } switch (marker) { case JpegMarker.StartOfImage: writer.WriteMarker(marker); break; case JpegMarker.App0: case JpegMarker.StartOfFrame0: case JpegMarker.StartOfFrame1: writer.WriteMarker(marker); CopyMarkerData(ref reader, ref writer); break; case JpegMarker.StartOfFrame2: ProcessFrameHeader(ref reader, false, true); throw new InvalidDataException("Progressive JPEG is not supported currently."); case JpegMarker.StartOfFrame3: case JpegMarker.StartOfFrame5: case JpegMarker.StartOfFrame6: case JpegMarker.StartOfFrame7: case JpegMarker.StartOfFrame9: case JpegMarker.StartOfFrame10: case JpegMarker.StartOfFrame11: case JpegMarker.StartOfFrame13: case JpegMarker.StartOfFrame14: case JpegMarker.StartOfFrame15: ThrowInvalidDataException(reader.ConsumedByteCount, $"This type of JPEG stream is not supported ({marker})."); return; case JpegMarker.DefineHuffmanTable: if (!huffmanTableWritten) { WriteHuffmanTables(ref writer); huffmanTableWritten = true; } break; case JpegMarker.DefineQuantizationTable: if (!quantizationTableWritten) { WriteQuantizationTables(ref writer); quantizationTableWritten = true; } break; case JpegMarker.StartOfScan: writer.WriteMarker(marker); ReadOnlySequence <byte> buffer = CopyMarkerData(ref reader, ref writer); if (!JpegScanHeader.TryParse(buffer, false, out JpegScanHeader scanHeader, out _)) { ThrowInvalidDataException(reader.ConsumedByteCount - (int)buffer.Length, "Failed to parse scan header."); } CopyScanBaseline(ref reader, ref writer, scanHeader); break; case JpegMarker.DefineRestart0: case JpegMarker.DefineRestart1: case JpegMarker.DefineRestart2: case JpegMarker.DefineRestart3: case JpegMarker.DefineRestart4: case JpegMarker.DefineRestart5: case JpegMarker.DefineRestart6: case JpegMarker.DefineRestart7: writer.WriteMarker(marker); break; case JpegMarker.EndOfImage: writer.WriteMarker(JpegMarker.EndOfImage); eoiReached = true; break; default: if (strip) { SkipMarkerData(ref reader); } else { writer.WriteMarker(marker); CopyMarkerData(ref reader, ref writer); } break; } } writer.Flush(); }