/*
|
* 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
|