SkJpegSegmentScanner: Use state-machine based scan
Separate what had previously been SkJpegSegmentScan into two classes.
The first is SkJpegSegmentScanner. This is a state machine that is
fed bytes and tracks the JPEG segment structure of the data that has
been provided to it. This should be significantly more efficient than
the previous scanning structure, because it does not do any stream
operations itself.
The second is SkJpegSeekableScan. This is created from a seekable
SkStream. It uses SkJpegSegmentScanner to determine the JPEG segment
structure of the data, and provides utility functions for retrieving
parameters and sub-streams.
This is towards supporting decoding JpegR gainmaps from unseekable
and unrewindable streams.
Bug: skia:14031
Change-Id: Icdb36dae6c3bbb2e282ade1cd5469e171d7e8001
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/633456
Commit-Queue: Christopher Cameron <ccameron@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/src/codec/SkJpegGainmap.cpp b/src/codec/SkJpegGainmap.cpp
index 2f9c894..c28c19b 100644
--- a/src/codec/SkJpegGainmap.cpp
+++ b/src/codec/SkJpegGainmap.cpp
@@ -237,7 +237,7 @@
}
// Scan the original decoder stream.
- auto scan = SkJpegSegmentScan::Create(decoderStream, SkJpegSegmentScan::Options());
+ auto scan = SkJpegSeekableScan::Create(decoderStream);
if (!scan) {
SkCodecPrintf("Failed to scan decoder stream.\n");
return false;
@@ -258,8 +258,7 @@
}
// Create a scan of this MP image.
- auto mpImageScan =
- SkJpegSegmentScan::Create(mpImage.stream.get(), SkJpegSegmentScan::Options());
+ auto mpImageScan = SkJpegSeekableScan::Create(mpImage.stream.get());
if (!mpImageScan) {
SkCodecPrintf("Failed to can MP image.\n");
continue;
@@ -469,15 +468,13 @@
// The offset read from the XMP metadata is relative to the end of the EndOfImage marker in the
// original decoder stream. Create a full scan of the original decoder stream, so we can find
// that EndOfImage marker's offset in the decoder stream.
- SkJpegSegmentScan::Options options;
- options.stopOnStartOfScan = false;
- auto scan = SkJpegSegmentScan::Create(decoderStream, options);
+ auto scan = SkJpegSeekableScan::Create(decoderStream, SkJpegSegmentScanner::kMarkerEndOfImage);
if (!scan) {
SkCodecPrintf("Failed to do full scan.\n");
return false;
}
const auto& lastSegment = scan->segments().back();
- const size_t endOfImageOffset = lastSegment.offset + SkJpegSegmentScan::kMarkerCodeSize;
+ const size_t endOfImageOffset = lastSegment.offset + SkJpegSegmentScanner::kMarkerCodeSize;
const size_t itemOffsetFromStartOfImage = endOfImageOffset + itemOffsetFromEndOfImage;
// Extract the gainmap image's stream.
diff --git a/src/codec/SkJpegMultiPicture.cpp b/src/codec/SkJpegMultiPicture.cpp
index 7b704b5..f36c985 100644
--- a/src/codec/SkJpegMultiPicture.cpp
+++ b/src/codec/SkJpegMultiPicture.cpp
@@ -176,7 +176,7 @@
}
std::unique_ptr<SkJpegMultiPictureStreams> SkJpegExtractMultiPictureStreams(
- SkJpegSegmentScan* scan) {
+ SkJpegSeekableScan* scan) {
// Look through the scanned segments until we arrive at a MultiPicture segment that we can
// parse.
size_t mpSegmentOffset = 0;
@@ -208,8 +208,8 @@
if (imageParams.dataOffset == 0) {
continue;
}
- size_t imageStreamOffset = mpSegmentOffset + SkJpegSegmentScan::kMarkerCodeSize +
- SkJpegSegmentScan::kParameterLengthSize + sizeof(kMpfSig) +
+ size_t imageStreamOffset = mpSegmentOffset + SkJpegSegmentScanner::kMarkerCodeSize +
+ SkJpegSegmentScanner::kParameterLengthSize + sizeof(kMpfSig) +
imageParams.dataOffset;
size_t imageStreamSize = imageParams.size;
result->images[i].stream = scan->getSubsetStream(imageStreamOffset, imageStreamSize);
diff --git a/src/codec/SkJpegMultiPicture.h b/src/codec/SkJpegMultiPicture.h
index 417f5ca..5140f97 100644
--- a/src/codec/SkJpegMultiPicture.h
+++ b/src/codec/SkJpegMultiPicture.h
@@ -16,7 +16,7 @@
#include <vector>
class SkData;
-class SkJpegSegmentScan;
+class SkJpegSeekableScan;
/*
* Parsed Jpeg Multi-Picture Format structure as specified in CIPA DC-x007-2009. An introduction to
@@ -47,7 +47,7 @@
const sk_sp<const SkData>& data);
/*
- * Create SkStreams for all MultiPicture images, given a SkJpegSegmentScan of the image. This will
+ * Create SkStreams for all MultiPicture images, given a SkJpegSeekableScan of the image. This will
* return nullptr if there is not MultiPicture segment, or if the MultiPicture parameters fail to
* parse.
*/
@@ -64,6 +64,6 @@
std::vector<Image> images;
};
std::unique_ptr<SkJpegMultiPictureStreams> SkJpegExtractMultiPictureStreams(
- SkJpegSegmentScan* scan);
+ SkJpegSeekableScan* scan);
#endif
diff --git a/src/codec/SkJpegSegmentScan.cpp b/src/codec/SkJpegSegmentScan.cpp
index 50cbdb3..dc610e5 100644
--- a/src/codec/SkJpegSegmentScan.cpp
+++ b/src/codec/SkJpegSegmentScan.cpp
@@ -16,42 +16,214 @@
#include <cstring>
#include <utility>
-// Some seekable SkStream sub-classes do not implement peek. Manually implement it via
-// setting and restoring the stream's position.
-static size_t peek(SkStream* stream, void* data, size_t size) {
- size_t initialPosition = stream->getPosition();
- size_t result = stream->read(data, size);
- if (!stream->seek(initialPosition)) {
- SkCodecPrintf("Failed to restore position during peek.\n");
- return 0;
- }
- return result;
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// SkJpegSegmentScanner
+
+SkJpegSegmentScanner::SkJpegSegmentScanner(uint8_t stopMarker) : fStopMarker(stopMarker) {}
+
+size_t SkJpegSegmentScanner::bytesSinceDone() const {
+ SkASSERT(fState == State::kDone);
+ return fOffset - fSegments.back().offset - kMarkerCodeSize;
}
-SkJpegSegmentScan::SkJpegSegmentScan(SkStream* stream,
- size_t initialPosition,
- std::vector<Segment>&& segments)
+const std::vector<SkJpegSegment>& SkJpegSegmentScanner::getSegments() const {
+ SkASSERT(fState == State::kDone);
+ return fSegments;
+}
+
+void SkJpegSegmentScanner::onBytes(const void* data, size_t size) {
+ const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data);
+ size_t bytesRemaining = size;
+
+ while (bytesRemaining > 0) {
+ // Process the data byte-by-byte, unless we are in kSegmentParam or kEntropyCodedData, in
+ // which case, perform some optimizations to avoid examining every byte.
+ size_t bytesToMoveForward = 0;
+ switch (fState) {
+ case State::kSegmentParam: {
+ // Skip forward through payloads.
+ SkASSERT(fSegmentParamBytesRemaining > 0);
+ bytesToMoveForward = std::min(fSegmentParamBytesRemaining, bytesRemaining);
+ fSegmentParamBytesRemaining -= bytesToMoveForward;
+ if (fSegmentParamBytesRemaining == 0) {
+ fState = State::kEntropyCodedData;
+ }
+ break;
+ }
+ case State::kEntropyCodedData: {
+ // Skip through entropy-coded data, only looking at sentinel characters.
+ const uint8_t* sentinel =
+ reinterpret_cast<const uint8_t*>(memchr(bytes, 0xFF, bytesRemaining));
+ if (sentinel) {
+ bytesToMoveForward = (sentinel - bytes) + 1;
+ fState = State::kEntropyCodedDataSentinel;
+ } else {
+ bytesToMoveForward = bytesRemaining;
+ }
+ break;
+ }
+ case State::kDone:
+ // Skip all data after we have hit our stop marker.
+ bytesToMoveForward = bytesRemaining;
+ break;
+ default: {
+ onByte(*bytes);
+ bytesToMoveForward = 1;
+ break;
+ }
+ }
+ SkASSERT(bytesToMoveForward > 0);
+ fOffset += bytesToMoveForward;
+ bytes += bytesToMoveForward;
+ bytesRemaining -= bytesToMoveForward;
+ }
+}
+
+void SkJpegSegmentScanner::saveCurrentSegment(uint16_t length) {
+ SkJpegSegment s = {fCurrentSegmentOffset, fCurrentSegmentMarker, length};
+ fSegments.push_back(s);
+
+ fCurrentSegmentMarker = 0;
+ fCurrentSegmentOffset = 0;
+}
+
+void SkJpegSegmentScanner::onMarkerSecondByte(uint8_t byte) {
+ SkASSERT(fState == State::kStartOfImageByte1 || fState == State::kSecondMarkerByte1 ||
+ fState == State::kEntropyCodedDataSentinel ||
+ fState == State::kPostEntropyCodedDataFill);
+
+ fCurrentSegmentMarker = byte;
+ fCurrentSegmentOffset = fOffset - 1;
+
+ if (byte == fStopMarker) {
+ saveCurrentSegment(0);
+ fState = State::kDone;
+ } else if (byte == kMarkerStartOfImage) {
+ saveCurrentSegment(0);
+ fState = State::kSecondMarkerByte0;
+ } else if (MarkerStandsAlone(byte)) {
+ saveCurrentSegment(0);
+ fState = State::kEntropyCodedData;
+ } else {
+ fCurrentSegmentMarker = byte;
+ fState = State::kSegmentParamLengthByte0;
+ }
+}
+
+void SkJpegSegmentScanner::onByte(uint8_t byte) {
+ switch (fState) {
+ case State::kStartOfImageByte0:
+ if (byte != 0xFF) {
+ SkCodecPrintf("First byte was %02x, not 0xFF", byte);
+ fState = State::kError;
+ return;
+ }
+ fState = State::kStartOfImageByte1;
+ break;
+ case State::kStartOfImageByte1:
+ if (byte != kMarkerStartOfImage) {
+ SkCodecPrintf("Second byte was %02x, not %02x", byte, kMarkerStartOfImage);
+ fState = State::kError;
+ return;
+ }
+ onMarkerSecondByte(byte);
+ break;
+ case State::kSecondMarkerByte0:
+ if (byte != 0xFF) {
+ SkCodecPrintf("Third byte was %02x, not 0xFF", byte);
+ fState = State::kError;
+ return;
+ }
+ fState = State::kSecondMarkerByte1;
+ break;
+ case State::kSecondMarkerByte1:
+ // See section B.1.1.3: All markers are assigned two-byte codes: a 0xFF byte followed by
+ // a byte which is not equal to 0x00 or 0xFF.
+ if (byte == 0xFF || byte == 0x00) {
+ SkCodecPrintf("SkJpegSegment marker was 0xFF,0xFF or 0xFF,0x00");
+ fState = State::kError;
+ return;
+ }
+ onMarkerSecondByte(byte);
+ break;
+ case State::kSegmentParamLengthByte0:
+ fSegmentParamLengthByte0 = byte;
+ fState = State::kSegmentParamLengthByte1;
+ break;
+ case State::kSegmentParamLengthByte1: {
+ uint16_t paramLength = 256u * fSegmentParamLengthByte0 + byte;
+ fSegmentParamLengthByte0 = 0;
+
+ // See section B.1.1.4: A marker segment consists of a marker followed by a sequence
+ // of related parameters. The first parameter in a marker segment is the two-byte length
+ // parameter. This length parameter encodes the number of bytes in the marker segment,
+ // including the length parameter and excluding the two-byte marker.
+ if (paramLength < kParameterLengthSize) {
+ SkCodecPrintf("SkJpegSegment payload length was %u < 2 bytes", paramLength);
+ fState = State::kError;
+ return;
+ }
+ saveCurrentSegment(paramLength);
+ fSegmentParamBytesRemaining = paramLength - kParameterLengthSize;
+ if (fSegmentParamBytesRemaining > 0) {
+ fState = State::kSegmentParam;
+ } else {
+ fState = State::kEntropyCodedData;
+ }
+ break;
+ }
+ case State::kSegmentParam:
+ SkASSERT(fSegmentParamBytesRemaining > 0);
+ fSegmentParamBytesRemaining -= 1;
+ if (fSegmentParamBytesRemaining == 0) {
+ fState = State::kEntropyCodedData;
+ }
+ break;
+ case State::kEntropyCodedData:
+ if (byte == 0xFF) {
+ fState = State::kEntropyCodedDataSentinel;
+ }
+ break;
+ case State::kEntropyCodedDataSentinel:
+ if (byte == 0x00) {
+ fState = State::kEntropyCodedData;
+ } else if (byte == 0xFF) {
+ fState = State::kPostEntropyCodedDataFill;
+ } else {
+ onMarkerSecondByte(byte);
+ }
+ break;
+ case State::kPostEntropyCodedDataFill:
+ // See section B.1.1.3: Any marker may optionally be preceded by any number of fill
+ // bytes, which are bytes assigned code 0xFF. Skip past any 0xFF fill bytes that may be
+ // present at the end of the entropy-coded data.
+ if (byte == 0xFF) {
+ fState = State::kPostEntropyCodedDataFill;
+ } else if (byte == 0x00) {
+ SkCodecPrintf("Post entropy coded data had 0xFF,0x00");
+ fState = State::kError;
+ return;
+ } else {
+ onMarkerSecondByte(byte);
+ }
+ break;
+ case State::kDone:
+ break;
+ case State::kError:
+ break;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// SkJpegSeekableScan
+
+SkJpegSeekableScan::SkJpegSeekableScan(SkStream* stream,
+ size_t initialPosition,
+ std::vector<SkJpegSegment>&& segments)
: fStream(stream), fInitialPosition(initialPosition), fSegments(segments) {}
-// static
-bool SkJpegSegmentScan::MarkerIsValid(const uint8_t markerCode[kMarkerCodeSize]) {
- if (markerCode[0] != 0xFF) return false;
- if (markerCode[1] == 0x00 || markerCode[1] == 0xFF) return false;
- return true;
-}
-
-// static
-bool SkJpegSegmentScan::MarkerStandsAlone(uint8_t marker) {
- // These markers are TEM (0x01), RSTm (0xD0 through 0xD7), SOI (0xD8), and
- // EOI (0xD9). See section B.1.1.3, Marker assignments.
- return marker == 0x01 || (marker >= 0xD0 && marker <= 0xD9);
-}
-
-std::unique_ptr<SkJpegSegmentScan> SkJpegSegmentScan::Create(SkStream* stream,
- const Options& options) {
- size_t startOfImageCount = 0;
- size_t endOfImageCount = 0;
- std::vector<Segment> segments;
+std::unique_ptr<SkJpegSeekableScan> SkJpegSeekableScan::Create(SkStream* stream,
+ uint8_t stopMarker) {
size_t initialPosition = stream->getPosition();
// This implementation relies on using getPosition. It is possible to implement this with just
@@ -61,152 +233,36 @@
return nullptr;
}
- // First peek to see the Jpeg signature.
- {
- uint8_t signature[sizeof(kJpegSig)];
- if (peek(stream, signature, sizeof(kJpegSig)) != sizeof(kJpegSig)) {
- SkCodecPrintf("Failed to peek Jpeg signature.\n");
- return nullptr;
- }
- if (memcmp(signature, kJpegSig, sizeof(kJpegSig)) != 0) {
- SkCodecPrintf("Stream does not have Jpeg signature.\n");
- return nullptr;
+ SkJpegSegmentScanner scanner(stopMarker);
+ if (stream->hasLength() && stream->getMemoryBase()) {
+ // If this stream is in-memory, scan it in one go.
+ scanner.onBytes(stream->getMemoryBase(), stream->getLength());
+ } else {
+ // Otherise, read it in 1k chunks.
+ constexpr size_t kBufferSize = 1024;
+ uint8_t buffer[kBufferSize];
+ while (!scanner.isDone() && !scanner.hadError()) {
+ size_t bytesRead = stream->read(buffer, kBufferSize);
+ if (bytesRead == 0) {
+ SkCodecPrintf("Unexpected EOF.\n");
+ }
+ scanner.onBytes(buffer, bytesRead);
}
}
-
- while (1) {
- // Read each segment, starting with its marker. See section B.1.1.3: All markers are
- // assigned two-byte codes: a 0xFF byte followed by a byte which is not equal to 0x00 or
- // 0xFF.
- const size_t offset = stream->getPosition() - initialPosition;
- uint8_t markerCode[kMarkerCodeSize] = {0};
- if (stream->read(markerCode, kMarkerCodeSize) != kMarkerCodeSize) {
- SkCodecPrintf("Unexpected EOF at first marker code.\n");
- break;
- }
- if (!MarkerIsValid(markerCode)) {
- SkCodecPrintf("Invalid marker code %02x%02x.\n", markerCode[0], markerCode[1]);
- return nullptr;
- }
-
- // We are at a marker for the beginning of a segment.
- Segment segment;
- segment.marker = markerCode[1];
- segment.offset = offset;
-
- // Some markers stand alone, which means that they do not have parameters, see Marker
- // assignments, in section B.1.1.3.
- if (!MarkerStandsAlone(segment.marker)) {
- uint8_t parameterLength[kParameterLengthSize] = {0};
- // Read the length for the parameters. See section B.1.1.4: A marker segment consists of
- // a marker followed by a sequence of related parameters. The first parameter in a
- // marker segment is the two-byte length parameter. This length parameter encodes the
- // number of bytes in the marker segment, including the length parameter and excluding
- // the two-byte marker.
- if (peek(stream, parameterLength, kParameterLengthSize) != kParameterLengthSize) {
- SkCodecPrintf("Failed to peek segment parameter length.\n");
- return nullptr;
- }
- segment.parameterLength = 256 * parameterLength[0] + parameterLength[1];
- if (stream->skip(segment.parameterLength) != segment.parameterLength) {
- SkCodecPrintf("Failed to seek past segment parameters.\n");
- return nullptr;
- }
- }
- segments.push_back(segment);
-
- // Validate StartOfImage and EndOfImage marker pairs, and stop reading when our termination
- // conditions are met.
- if (segment.marker == kMarkerStartOfImage) {
- startOfImageCount += 1;
- if (startOfImageCount != endOfImageCount + 1) {
- SkCodecPrintf("Encountered new StartOfImage without EndOfImage.\n");
- return nullptr;
- }
- } else if (segment.marker == kMarkerEndOfImage) {
- endOfImageCount += 1;
- if (endOfImageCount > startOfImageCount) {
- SkCodecPrintf("EndOfImage without StartOfImage.\n");
- return nullptr;
- }
- if (options.stopOnEndOfImageCount == endOfImageCount) {
- break;
- }
- } else if (segment.marker == kMarkerStartOfScan) {
- if (options.stopOnStartOfScan) {
- break;
- }
- }
-
- // Allow entroy-coded data to follow any segment.
- if (!SkipPastEntropyCodedData(stream)) {
- SkCodecPrintf("Failed to seek past entropy coded data.\n");
- return nullptr;
- }
+ if (scanner.hadError()) {
+ return nullptr;
}
+ std::vector<SkJpegSegment> segments = scanner.getSegments();
- return std::unique_ptr<SkJpegSegmentScan>(
- new SkJpegSegmentScan(stream, initialPosition, std::move(segments)));
+ return std::unique_ptr<SkJpegSeekableScan>(
+ new SkJpegSeekableScan(stream, initialPosition, std::move(segments)));
}
-bool SkJpegSegmentScan::SkipPastEntropyCodedData(SkStream* stream) {
- uint8_t markerCode[kMarkerCodeSize] = {0};
-
- while (1) {
- // Peek at the two bytes for the marker.
- if (peek(stream, markerCode, kMarkerCodeSize) != kMarkerCodeSize) {
- // Assume to be EOF.
- SkCodecPrintf("Failed to peek two ECD bytes (unexpected EOF?).\n");
- return false;
- }
-
- // If the first byte is not 0xFF, it's part of the entropy-coded data,
- // so skip to the next byte.
- if (markerCode[0] != 0xFF) {
- if (stream->skip(1) != 1) {
- SkCodecPrintf("Failed to skip single ECD byte.\n");
- return false;
- }
- continue;
- }
-
- // If the byte after the 0xFF is 0x00, assume we are in an entropy-coding
- // segment and that this is a byte-stuffed representation of 0xFF. See the
- // text of F.1.2.3: Whenever the byte value 0xFF is created in the code
- // string, a 0x00 byte is stuffed into the code string. If a 0x00 byte is
- // detected after a 0xFF byte, the decoder must discard it.
- if (markerCode[1] == 0x00) {
- if (stream->skip(kMarkerCodeSize) != kMarkerCodeSize) {
- SkCodecPrintf("Failed to skip stuffed ECD byte.\n");
- return false;
- }
- continue;
- }
-
- // We are at the end of the entropy-coded data.
- break;
- }
-
- // See section B.1.1.3: Any marker may optionally be preceded by any number
- // of fill bytes, which are bytes assigned code 0xFF. Skip past any 0xFF
- // fill bytes that may be present at the end of the entropy-coded data.
- while (markerCode[1] == 0xFF) {
- if (stream->skip(1) != 1) {
- SkCodecPrintf("Failed to skip post-ECD fill.\n");
- return false;
- }
- if (peek(stream, markerCode, kMarkerCodeSize) != kMarkerCodeSize) {
- SkCodecPrintf("Failed to peek past post-ECD fill (unexpected EOF?).\n");
- return false;
- }
- }
-
- return true;
-}
-
-sk_sp<SkData> SkJpegSegmentScan::copyParameters(const Segment& segment,
- const void* signature,
- const size_t signatureLength) {
+sk_sp<SkData> SkJpegSeekableScan::copyParameters(const SkJpegSegment& segment,
+ const void* signature,
+ const size_t signatureLength) {
+ constexpr size_t kParameterLengthSize = SkJpegSegmentScanner::kParameterLengthSize;
+ constexpr size_t kMarkerCodeSize = SkJpegSegmentScanner::kMarkerCodeSize;
// If the segment's parameter length isn't long enough for the signature and the length,
// early-out.
if (segment.parameterLength < signatureLength + kParameterLengthSize) {
@@ -257,7 +313,7 @@
return result;
}
-std::unique_ptr<SkStream> SkJpegSegmentScan::getSubsetStream(size_t offset, size_t size) {
+std::unique_ptr<SkStream> SkJpegSeekableScan::getSubsetStream(size_t offset, size_t size) {
// Read the image's data. It would be better to fork `stream` and limit its position and size.
if (!fStream->seek(fInitialPosition + offset)) {
SkCodecPrintf("Failed to seek to subset stream position.\n");
diff --git a/src/codec/SkJpegSegmentScan.h b/src/codec/SkJpegSegmentScan.h
index c55dd89..ce7fa7c 100644
--- a/src/codec/SkJpegSegmentScan.h
+++ b/src/codec/SkJpegSegmentScan.h
@@ -19,48 +19,44 @@
class SkStream;
/*
- * Return a scan of the segments of a JPEG image. The JPEG format consists of a sequence of segments
- * that begin with a marker, with entropy-coded data in between. This class will return the segment
- * structure for a JPEG file represented by a seekable SkStream. It can then be used to extract the
- * parameters for any segment, as long as the original SkStream is still valid.
+ * A JPEG segment.
*/
-class SkJpegSegmentScan {
+struct SkJpegSegment {
+ // The offset in bytes from the initial position to where this segment starts.
+ size_t offset = 0;
+ // The second byte of the marker code, which determines the segment type.
+ uint8_t marker = 0;
+ // The length of the parameters for this segment (including the two bytes used to specify
+ // the length).
+ uint16_t parameterLength = 0;
+};
+
+/*
+ * Class for scanning JPEG data. The JPEG format consists of a sequence of segments that begin with
+ * a marker, with entropy-coded data in between.
+ */
+class SkJpegSegmentScanner {
public:
- struct Segment {
- // The offset in bytes from the stream's initial position where this segment starts.
- size_t offset = 0;
- // The second byte of the marker code, which determines the segment type.
- uint8_t marker = 0;
- // The length of the parameters for this segment (including the two bytes used to specify
- // the length).
- uint16_t parameterLength = 0;
- };
+ SkJpegSegmentScanner(uint8_t stopMarker = kMarkerEndOfImage);
- struct Options {
- // If true, then stop the scan when the first StartOfScan marker is read.
- bool stopOnStartOfScan = true;
- // Stop the scan when the specified number of EndOfImage markers are read.
- size_t stopOnEndOfImageCount = 1;
- };
+ bool isDone() const { return fState == State::kDone; }
+ bool hadError() const { return fState == State::kError; }
- // Scan the stream, starting at its current position (not rewinding first), with the termination
- // conditions specified in the indicated options. This will return an object only when the scan
- // reached its termination conditions without encountering any errors.
- static std::unique_ptr<SkJpegSegmentScan> Create(SkStream* stream, const Options& options);
+ // Provide more bytes of data to the state machine.
+ void onBytes(const void* data, size_t size);
- // Return the list of segments from a scan.
- const std::vector<Segment>& segments() { return fSegments; }
+ // Return the number of bytes that have been received via OnBytes since the marker that caused
+ // the transition to kDone was read. This will assert that the state is kDone.
+ size_t bytesSinceDone() const;
- // Copy the parameters from a segment. Return nullptr if the initial bytes of the parameters
- // section do not match the specified signature. Return the parameters starting from end of the
- // signature (so, kParameterLengthSize + signatureLength bytes into the parameter data).
- sk_sp<SkData> copyParameters(const Segment& segment,
- const void* signature,
- const size_t signatureLength);
+ // Return the segments that have been retrieved so far. This will assert that no errors have
+ // been encountered.
+ const std::vector<SkJpegSegment>& getSegments() const;
- // Return a stream for a subset of the original stream, starting at the specified offset, and
- // with the specified length.
- std::unique_ptr<SkStream> getSubsetStream(size_t offset, size_t size);
+ // Convenient markers to know.
+ static constexpr uint8_t kMarkerStartOfImage = 0xD8;
+ static constexpr uint8_t kMarkerEndOfImage = 0xD9;
+ static constexpr uint8_t kMarkerStartOfScan = 0xDA;
// The number of bytes in a marker code is two.
static constexpr size_t kMarkerCodeSize = 2;
@@ -69,27 +65,116 @@
static constexpr size_t kParameterLengthSize = 2;
private:
- SkJpegSegmentScan(SkStream* stream, size_t initialPosition, std::vector<Segment>&&);
+ // The scanner is a state machine. State transitions happen when a byte is read.
+ enum class State {
+ // The initial state, before we read the 0xFF,0xD8,0xFF JPEG signature.
+ kStartOfImageByte0,
+ // We have read the 0xFF of the JPEG signature.
+ kStartOfImageByte1,
+ // We have read the 0xFF,0xD8 of the JPEG signature. The next byte should be the 0xFF that
+ // completes the JPEG signature and starts the second marker (the one after StartOfImage).
+ kSecondMarkerByte0,
+ // We have read the full JPEG signature. The next byte should be the the second byte of the
+ // second marker.
+ kSecondMarkerByte1,
+ // We have read a marker that does not stand alone. The next byte is the first byte of the
+ // length of the parameters,
+ kSegmentParamLengthByte0,
+ // We have read the first byte of the length of the parameters (and it is stored in
+ // |fSegmentParamLengthByte0|). The next byte we read is the second byte of the length of
+ // the parameters.
+ kSegmentParamLengthByte1,
+ // We have read the full length of the parameters. The next |fSegmentParamBytesRemaining|
+ // bytes are the parameters.
+ kSegmentParam,
+ // We have read a marker and (optionally) its parameters, and we are now reading entropy-
+ // coded data. All subsequent bytes until we reach 0xFF are entropy-coded data.
+ kEntropyCodedData,
+ // We reached an 0xFF in entropy-coded data. If the next byte is 0x00 then we continue
+ // reading entropy-coded data. If the next byte is 0xFF then we are reading fill data.
+ // If the next byte is anything else then it is the second byte of a marker.
+ kEntropyCodedDataSentinel,
+ // We are reading fill data. If the next byte is 0xFF then we are still reading fill data,
+ // otherwise the next byte is the second byte of a marker.
+ kPostEntropyCodedDataFill,
+ // We reached |fStopMarker| and have stopped tracking our state.
+ kDone,
+ // We hit an error somewhere and have given up.
+ kError,
+ };
+ State fState = State::kStartOfImageByte0;
- // Skip past any entropy-coded data. If this function returns true, then the stream is
- // positioned to read the next (potentially valid) marker code. If the function returns false,
- // then there was a read failure.
- static bool SkipPastEntropyCodedData(SkStream* stream);
+ // Update state transition when a single byte is read.
+ void onByte(uint8_t byte);
- // Returns true if this is a valid marker code (starts with 0xFF and is not 0xFF,0x00 or
- // 0xFF,0xFF).
- static bool MarkerIsValid(const uint8_t markerCode[kMarkerCodeSize]);
+ // Perform the appropriate state transition for when a marker is read. This will set
+ // |fCurrentSegmentMarker| and |fCurrentSegmentOffset|, and potentially call saveCurrentSegment.
+ void onMarkerSecondByte(uint8_t byte);
- // Returns true if this marker stand alone, which means that it does not have parameters.
- static bool MarkerStandsAlone(uint8_t marker);
+ // Add a new entry in |segments| for |fCurrentSegmentMarker| and offset |fCurrentSegmentOffset|
+ // and the specified length.
+ void saveCurrentSegment(uint16_t length);
- static constexpr uint8_t kMarkerStartOfImage = 0xD8;
- static constexpr uint8_t kMarkerEndOfImage = 0xD9;
- static constexpr uint8_t kMarkerStartOfScan = 0xDA;
+ static bool MarkerStandsAlone(uint8_t marker) {
+ // These markers are TEM (0x01), RSTm (0xD0 through 0xD7), SOI (0xD8), and
+ // EOI (0xD9). See section B.1.1.3, Marker assignments.
+ return marker == 0x01 || (marker >= 0xD0 && marker <= 0xD9);
+ }
+
+ // Stop tracking state when we hit this marker. If this is 0x00, then never stop.
+ const uint8_t fStopMarker;
+
+ // The number of bytes that have been processed so far.
+ size_t fOffset = 0;
+
+ // If |fState| is kSegmentParamLengthByte1, then this is the value of the the previous byte.
+ uint8_t fSegmentParamLengthByte0 = 0;
+
+ // If |fState| is kSegmentParam, then this is the number of bytes reamining in the current
+ // segment.
+ size_t fSegmentParamBytesRemaining = 0;
+
+ // The offset and marker for the segment started by the previous call to OnMarkerSecondByte.
+ // These are re-set when SaveCurrentSegment is called.
+ size_t fCurrentSegmentOffset = 0;
+ uint8_t fCurrentSegmentMarker = 0;
+
+ std::vector<SkJpegSegment> fSegments;
+};
+
+/*
+ * This class will return the segment structure for a JPEG file represented by a seekable SkStream.
+ * It can then be used to extract the parameters for any segment, as long as the original SkStream
+ * is still valid.
+ */
+class SkJpegSeekableScan {
+public:
+ // Scan the stream, starting at its current position (not rewinding first), and stopping when
+ // the specified stop marker is reached. This will return nullptr if the stop marker is not
+ // reached.
+ static std::unique_ptr<SkJpegSeekableScan> Create(
+ SkStream* stream, uint8_t stopMarker = SkJpegSegmentScanner::kMarkerStartOfScan);
+
+ // Return the list of segments from a scan.
+ const std::vector<SkJpegSegment>& segments() { return fSegments; }
+
+ // Copy the parameters from a segment. Return nullptr if the initial bytes of the parameters
+ // section do not match the specified signature. Return the parameters starting from end of the
+ // signature (so, kParameterLengthSize + signatureLength bytes into the parameter data).
+ sk_sp<SkData> copyParameters(const SkJpegSegment& segment,
+ const void* signature,
+ const size_t signatureLength);
+
+ // Return a stream for a subset of the original stream, starting at the specified offset, and
+ // with the specified length.
+ std::unique_ptr<SkStream> getSubsetStream(size_t offset, size_t size);
+
+private:
+ SkJpegSeekableScan(SkStream* stream, size_t initialPosition, std::vector<SkJpegSegment>&&);
SkStream* const fStream;
const size_t fInitialPosition;
- const std::vector<Segment> fSegments;
+ const std::vector<SkJpegSegment> fSegments;
};
#endif
diff --git a/tests/JpegGainmapTest.cpp b/tests/JpegGainmapTest.cpp
index aa8c8ca..e06d07e 100644
--- a/tests/JpegGainmapTest.cpp
+++ b/tests/JpegGainmapTest.cpp
@@ -63,14 +63,13 @@
}
// Ensure that we get the expected number of segments for a scan that stops at StartOfScan.
- SkJpegSegmentScan::Options options;
- auto sosSegmentScan = SkJpegSegmentScan::Create(stream.get(), options);
+ auto sosSegmentScan = SkJpegSeekableScan::Create(stream.get());
REPORTER_ASSERT(r, rec.sosSegmentCount == sosSegmentScan->segments().size());
// Rewind and now go all the way to EndOfImage.
stream->rewind();
- options.stopOnStartOfScan = false;
- auto eoiSegmentScan = SkJpegSegmentScan::Create(stream.get(), options);
+ auto eoiSegmentScan =
+ SkJpegSeekableScan::Create(stream.get(), SkJpegSegmentScanner::kMarkerEndOfImage);
REPORTER_ASSERT(r, rec.eoiSegmentCount == eoiSegmentScan->segments().size());
// Verify the values for a randomly pre-selected segment index.
@@ -86,7 +85,7 @@
auto stream = GetResourceAsStream(path);
REPORTER_ASSERT(r, stream);
- auto segmentScan = SkJpegSegmentScan::Create(stream.get(), SkJpegSegmentScan::Options());
+ auto segmentScan = SkJpegSeekableScan::Create(stream.get());
REPORTER_ASSERT(r, segmentScan);
// Extract the streams for the MultiPicture images.
@@ -160,7 +159,7 @@
-2.209409f,
2.209409f,
1.f,
- 9.110335,
+ 9.110335f,
SkGainmapInfo::Type::kHDRGM},
};