blob: dc610e51c24fbedf72e739d11bef1545b51c4e4a [file] [log] [blame]
/*
* Copyright 2023 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/codec/SkJpegSegmentScan.h"
#include "include/core/SkData.h"
#include "include/core/SkStream.h"
#include "include/private/base/SkAssert.h"
#include "src/codec/SkCodecPriv.h"
#include "src/codec/SkJpegPriv.h"
#include <cstring>
#include <utility>
////////////////////////////////////////////////////////////////////////////////////////////////////
// SkJpegSegmentScanner
SkJpegSegmentScanner::SkJpegSegmentScanner(uint8_t stopMarker) : fStopMarker(stopMarker) {}
size_t SkJpegSegmentScanner::bytesSinceDone() const {
SkASSERT(fState == State::kDone);
return fOffset - fSegments.back().offset - kMarkerCodeSize;
}
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) {}
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
// rewind.
if (!stream->hasPosition()) {
SkCodecPrintf("Cannot scan stream that doesn't have a position.\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);
}
}
if (scanner.hadError()) {
return nullptr;
}
std::vector<SkJpegSegment> segments = scanner.getSegments();
return std::unique_ptr<SkJpegSeekableScan>(
new SkJpegSeekableScan(stream, initialPosition, std::move(segments)));
}
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) {
return nullptr;
}
size_t sizeToRead = segment.parameterLength - signatureLength - kParameterLengthSize;
// Seek to the start of the segment.
if (!fStream->seek(fInitialPosition + segment.offset)) {
SkCodecPrintf("Failed to seek to segment\n");
return nullptr;
}
// Read the marker and verify it matches `segment`.
uint8_t markerCode[kMarkerCodeSize] = {0};
if (fStream->read(markerCode, kMarkerCodeSize) != kMarkerCodeSize) {
SkCodecPrintf("Failed to read segment marker code\n");
return nullptr;
}
SkASSERT(markerCode[0] == 0xFF);
SkASSERT(markerCode[1] == segment.marker);
// Read the parameter length and verify it matches `segment`.
uint8_t parameterLength[kParameterLengthSize] = {0};
if (fStream->read(parameterLength, kParameterLengthSize) != kParameterLengthSize) {
SkCodecPrintf("Failed to read parameter length\n");
return nullptr;
}
SkASSERT(256 * parameterLength[0] + parameterLength[1] == segment.parameterLength);
// Check the next bytes against `signature`.
auto segmentSignature = SkData::MakeUninitialized(signatureLength);
if (fStream->read(segmentSignature->writable_data(), segmentSignature->size()) !=
segmentSignature->size()) {
SkCodecPrintf("Failed to read parameters\n");
return nullptr;
}
if (memcmp(segmentSignature->data(), signature, signatureLength) != 0) {
return nullptr;
}
// Finally, read the remainder of the segment.
auto result = SkData::MakeUninitialized(sizeToRead);
if (fStream->read(result->writable_data(), sizeToRead) != sizeToRead) {
return nullptr;
}
return result;
}
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");
return nullptr;
}
sk_sp<SkData> data = SkData::MakeUninitialized(size);
if (fStream->read(data->writable_data(), size) != size) {
SkCodecPrintf("Failed to read subset stream data.\n");
return nullptr;
}
return SkMemoryStream::Make(data);
}