blob: 72e659e71adf10a022d3b55857bcb72479bba787 [file] [log] [blame]
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkJpegInfo.h"
#include "SkTo.h"
#ifndef SK_HAS_JPEG_LIBRARY
namespace {
class JpegSegment {
public:
JpegSegment(const void* data, size_t size)
: fData(static_cast<const char*>(data))
, fSize(size)
, fOffset(0)
, fLength(0) {}
bool read() {
if (!this->readBigendianUint16(&fMarker)) {
return false;
}
if (JpegSegment::StandAloneMarker(fMarker)) {
fLength = 0;
fBuffer = nullptr;
return true;
}
if (!this->readBigendianUint16(&fLength) || fLength < 2) {
return false;
}
fLength -= 2; // Length includes itself for some reason.
if (fOffset + fLength > fSize) {
return false; // Segment too long.
}
fBuffer = &fData[fOffset];
fOffset += fLength;
return true;
}
bool isSOF() {
return (fMarker & 0xFFF0) == 0xFFC0 && fMarker != 0xFFC4 &&
fMarker != 0xFFC8 && fMarker != 0xFFCC;
}
uint16_t marker() { return fMarker; }
uint16_t length() { return fLength; }
const char* data() { return fBuffer; }
static uint16_t GetBigendianUint16(const char* ptr) {
// "the most significant byte shall come first"
return (static_cast<uint8_t>(ptr[0]) << 8) |
static_cast<uint8_t>(ptr[1]);
}
private:
const char* const fData;
const size_t fSize;
size_t fOffset;
const char* fBuffer;
uint16_t fMarker;
uint16_t fLength;
bool readBigendianUint16(uint16_t* value) {
if (fOffset + 2 > fSize) {
return false;
}
*value = JpegSegment::GetBigendianUint16(&fData[fOffset]);
fOffset += 2;
return true;
}
static bool StandAloneMarker(uint16_t marker) {
// RST[m] markers or SOI, EOI, TEM
return (marker & 0xFFF8) == 0xFFD0 || marker == 0xFFD8 ||
marker == 0xFFD9 || marker == 0xFF01;
}
};
} // namespace
bool SkGetJpegInfo(const void* data, size_t len,
SkISize* size,
SkEncodedInfo::Color* colorType,
SkEncodedOrigin* orientation) {
static const uint16_t kSOI = 0xFFD8;
static const uint16_t kAPP0 = 0xFFE0;
JpegSegment segment(data, len);
if (!segment.read() || segment.marker() != kSOI) {
return false; // not a JPEG
}
if (!segment.read() || segment.marker() != kAPP0) {
return false; // not an APP0 segment
}
static const char kJfif[] = {'J', 'F', 'I', 'F', '\0'};
SkASSERT(segment.data());
if (SkToSizeT(segment.length()) < sizeof(kJfif) ||
0 != memcmp(segment.data(), kJfif, sizeof(kJfif))) {
return false; // Not JFIF JPEG
}
do {
if (!segment.read()) {
return false; // malformed JPEG
}
} while (!segment.isSOF());
if (segment.length() < 6) {
return false; // SOF segment is short
}
if (8 != segment.data()[0]) {
return false; // Only support 8-bit precision
}
int numberOfComponents = segment.data()[5];
if (numberOfComponents != 1 && numberOfComponents != 3) {
return false; // Invalid JFIF
}
if (size) {
*size = {JpegSegment::GetBigendianUint16(&segment.data()[3]),
JpegSegment::GetBigendianUint16(&segment.data()[1])};
}
if (colorType) {
*colorType = numberOfComponents == 3 ? SkEncodedInfo::kYUV_Color
: SkEncodedInfo::kGray_Color;
}
if (orientation) {
*orientation = kTopLeft_SkEncodedOrigin;
}
return true;
}
#endif // SK_HAS_JPEG_LIBRARY