diff options
author | reed@android.com <reed@android.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2008-12-17 15:59:43 +0000 |
---|---|---|
committer | reed@android.com <reed@android.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2008-12-17 15:59:43 +0000 |
commit | 8a1c16ff38322f0210116fa7293eb8817c7e477e (patch) | |
tree | fe40e07f6c8983318a2f79032b9a706ede1090c1 /src/images | |
parent | 2559c629078f738ac37095d896d580b681ac6a30 (diff) |
grab from latest android
git-svn-id: http://skia.googlecode.com/svn/trunk@27 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'src/images')
27 files changed, 6410 insertions, 0 deletions
diff --git a/src/images/SkBitmap_RLEPixels.h b/src/images/SkBitmap_RLEPixels.h new file mode 100644 index 0000000000..c83bc69e7e --- /dev/null +++ b/src/images/SkBitmap_RLEPixels.h @@ -0,0 +1,19 @@ +#ifndef SkBitmap_RLEPixels_DEFINED +#define SkBitmap_RLEPixels_DEFINED + +#include "SkChunkAlloc.h" + +class SkBitmap_RLEPixels { +public: + SkBitmap_RLEPixels(int width, int height); + ~SkBitmap_RLEPixels(); + + uint8_t* yptrs() const { return fYPtrs; } + uint8_t* allocChunk(size_t chunk); + +private: + SkChunkAlloc fChunk; + uint8_t** fYPtrs; +}; + +#endif diff --git a/src/images/SkCreateRLEPixelRef.cpp b/src/images/SkCreateRLEPixelRef.cpp new file mode 100644 index 0000000000..575623749b --- /dev/null +++ b/src/images/SkCreateRLEPixelRef.cpp @@ -0,0 +1,120 @@ +#include "SkChunkAlloc.h" +#include "SkPackBits.h" +#include "SkBitmap.h" +#include "SkPixelRef.h" + +class RLEPixelRef : public SkPixelRef { +public: + RLEPixelRef(SkBitmap::RLEPixels* rlep, SkColorTable* ctable); + virtual ~RLEPixelRef(); + +protected: + // overrides from SkPixelRef + virtual void* onLockPixels(SkColorTable**); + virtual void onUnlockPixels(); + +private: + SkBitmap::RLEPixels* fRLEPixels; + SkColorTable* fCTable; +}; + +RLEPixelRef::RLEPixelRef(SkBitmap::RLEPixels* rlep, SkColorTable* ctable) + : SkPixelRef(NULL) { + fRLEPixels = rlep; // we now own this ptr + fCTable = ctable; + ctable->safeRef(); +} + +RLEPixelRef::~RLEPixelRef() { + SkDELETE(fRLEPixels); + fCTable->safeUnref(); +} + +void* RLEPixelRef::onLockPixels(SkColorTable** ct) { + *ct = fCTable; + return fRLEPixels; +} + +void RLEPixelRef::onUnlockPixels() { + // nothing to do +} + +///////////////////////////////////////////////////////////////////////////// + +class ChunkRLEPixels : public SkBitmap::RLEPixels { +public: + ChunkRLEPixels(int width, int height, size_t chunkSize) + : SkBitmap::RLEPixels(width, height), fStorage(chunkSize) { + } + + SkChunkAlloc fStorage; +}; + +SkPixelRef* SkCreateRLEPixelRef(const SkBitmap& src); +SkPixelRef* SkCreateRLEPixelRef(const SkBitmap& src) { + + if (SkBitmap::kIndex8_Config != src.config() && + SkBitmap::kA8_Config != src.config()) { + return NULL; + } + + size_t maxPacked = SkPackBits::ComputeMaxSize8(src.width()); + + // estimate the rle size based on the original size + size_t size = src.getSize() >> 3; + if (size < maxPacked) { + size = maxPacked; + } + + ChunkRLEPixels* rlePixels = SkNEW_ARGS(ChunkRLEPixels, + (src.width(), src.height(), size)); + + uint8_t* dstRow = NULL; + size_t free = 0; + size_t totalPacked = 0; + + for (int y = 0; y < src.height(); y++) { + const uint8_t* srcRow = src.getAddr8(0, y); + + if (free < maxPacked) { + dstRow = (uint8_t*)rlePixels->fStorage.allocThrow(size); + free = size; + } + size_t packedSize = SkPackBits::Pack8(srcRow, src.width(), dstRow); + SkASSERT(packedSize <= free); + rlePixels->setPackedAtY(y, dstRow); + + dstRow += packedSize; + free -= packedSize; + + totalPacked += packedSize; + } + +//#ifdef SK_DEBUG +#if 0 + // test + uint8_t* buffer = new uint8_t[src.width()]; + for (int y = 0; y < src.height(); y++) { + const uint8_t* srcRow = src.getAddr8(0, y); + SkPackBits::Unpack8(buffer, 0, src.width(), rlePixels->packedAtY(y)); + int n = memcmp(buffer, srcRow, src.width()); + if (n) { + SkDebugf("----- memcmp returned %d on line %d\n", n, y); + } + SkASSERT(n == 0); + } + delete[] buffer; + + size_t totalAlloc = src.height() * sizeof(uint8_t*) + totalPacked; + + SkDebugf("--- RLE: orig [%d %d] %d, rle %d %d savings %g\n", + src.width(), src.height(), src.getSize(), + src.height() * sizeof(uint8_t*), totalPacked, + (float)totalAlloc / src.getSize()); + +#endif + + // transfer ownership of rlePixels to our pixelref + return SkNEW_ARGS(RLEPixelRef, (rlePixels, src.getColorTable())); +} + diff --git a/src/images/SkFDStream.cpp b/src/images/SkFDStream.cpp new file mode 100644 index 0000000000..db4a51a60a --- /dev/null +++ b/src/images/SkFDStream.cpp @@ -0,0 +1,85 @@ +#include "SkStream.h" +#include <unistd.h> + +//#define TRACE_FDSTREAM + +SkFDStream::SkFDStream(int fileDesc, bool closeWhenDone) + : fFD(fileDesc), fCloseWhenDone(closeWhenDone) { +} + +SkFDStream::~SkFDStream() { + if (fFD >= 0 && fCloseWhenDone) { + ::close(fFD); + } +} + +bool SkFDStream::rewind() { + if (fFD >= 0) { + off_t value = ::lseek(fFD, 0, SEEK_SET); +#ifdef TRACE_FDSTREAM + if (value) { + SkDebugf("xxxxxxxxxxxxxx rewind failed %d\n", value); + } +#endif + return value == 0; + } + return false; +} + +size_t SkFDStream::read(void* buffer, size_t size) { + if (fFD >= 0) { + if (buffer == NULL && size == 0) { // request total size + off_t curr = ::lseek(fFD, 0, SEEK_CUR); + if (curr < 0) { +#ifdef TRACE_FDSTREAM + SkDebugf("xxxxxxxxxxxxx lseek failed 0 CURR\n"); +#endif + return 0; // error + } + off_t size = ::lseek(fFD, 0, SEEK_END); + if (size < 0) { +#ifdef TRACE_FDSTREAM + SkDebugf("xxxxxxxxxxxxx lseek failed 0 END\n"); +#endif + size = 0; // error + } + if (::lseek(fFD, curr, SEEK_SET) != curr) { + // can't restore, error +#ifdef TRACE_FDSTREAM + SkDebugf("xxxxxxxxxxxxx lseek failed %d SET\n", curr); +#endif + return 0; + } + return size; + } else if (NULL == buffer) { // skip + off_t oldCurr = ::lseek(fFD, 0, SEEK_CUR); + if (oldCurr < 0) { +#ifdef TRACE_FDSTREAM + SkDebugf("xxxxxxxxxxxxx lseek1 failed %d CUR\n", oldCurr); +#endif + return 0; // error; + } + off_t newCurr = ::lseek(fFD, size, SEEK_CUR); + if (newCurr < 0) { +#ifdef TRACE_FDSTREAM + SkDebugf("xxxxxxxxxxxxx lseek2 failed %d CUR\n", newCurr); +#endif + return 0; // error; + } + // return the actual amount we skipped + return newCurr - oldCurr; + } else { // read + ssize_t actual = ::read(fFD, buffer, size); + // our API can't return an error, so we return 0 + if (actual < 0) { +#ifdef TRACE_FDSTREAM + SkDebugf("xxxxxxxxxxxxx read failed %d actual %d\n", size, actual); +#endif + actual = 0; + } + return actual; + } + } + return 0; +} + diff --git a/src/images/SkFlipPixelRef.cpp b/src/images/SkFlipPixelRef.cpp new file mode 100644 index 0000000000..95403cc3f9 --- /dev/null +++ b/src/images/SkFlipPixelRef.cpp @@ -0,0 +1,127 @@ +#include "SkFlipPixelRef.h" +#include "SkFlattenable.h" +#include "SkRegion.h" + +SkFlipPixelRef::SkFlipPixelRef(SkBitmap::Config config, int width, int height) +: fFlipper(width, height) { + fConfig = config; + fSize = SkBitmap::ComputeSize(config, width, height); + fStorage = sk_malloc_throw(fSize << 1); + fPage0 = fStorage; + fPage1 = (char*)fStorage + fSize; +} + +SkFlipPixelRef::~SkFlipPixelRef() { + sk_free(fStorage); +} + +const SkRegion& SkFlipPixelRef::beginUpdate(SkBitmap* device) { + void* writeAddr; + const void* readAddr; + this->getFrontBack(&readAddr, &writeAddr); + + device->setConfig(fConfig, fFlipper.width(), fFlipper.height()); + device->setPixels(writeAddr); + + SkRegion copyBits; + const SkRegion& dirty = fFlipper.update(©Bits); + + SkFlipPixelRef::CopyBitsFromAddr(*device, copyBits, readAddr); + return dirty; +} + +void SkFlipPixelRef::endUpdate() { + this->swapPages(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void* SkFlipPixelRef::onLockPixels(SkColorTable** ct) { + fMutex.acquire(); + *ct = NULL; + return fPage0; +} + +void SkFlipPixelRef::onUnlockPixels() { + fMutex.release(); +} + +void SkFlipPixelRef::swapPages() { + fMutex.acquire(); + SkTSwap<void*>(fPage0, fPage1); + fMutex.release(); +} + +void SkFlipPixelRef::flatten(SkFlattenableWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + + buffer.write32(fSize); + // only need to write page0 + buffer.writePad(fPage0, fSize); +} + +SkFlipPixelRef::SkFlipPixelRef(SkFlattenableReadBuffer& buffer) + : INHERITED(buffer, NULL) { + fSize = buffer.readU32(); + fStorage = sk_malloc_throw(fSize << 1); + fPage0 = fStorage; + fPage1 = (char*)fStorage + fSize; + buffer.read(fPage0, fSize); +} + +SkPixelRef* SkFlipPixelRef::Create(SkFlattenableReadBuffer& buffer) { + return SkNEW_ARGS(SkFlipPixelRef, (buffer)); +} + +static SkPixelRef::Registrar::Registrar reg("SkFlipPixelRef", + SkFlipPixelRef::Create); + +/////////////////////////////////////////////////////////////////////////////// + +static void copyRect(const SkBitmap& dst, const SkIRect& rect, + const void* srcAddr, int shift) { + const size_t offset = rect.fTop * dst.rowBytes() + (rect.fLeft << shift); + char* dstP = static_cast<char*>(dst.getPixels()) + offset; + const char* srcP = static_cast<const char*>(srcAddr) + offset; + const size_t rb = dst.rowBytes(); + const size_t bytes = rect.width() << shift; + + int height = rect.height(); + while (--height >= 0) { + memcpy(dstP, srcP, bytes); + dstP += rb; + srcP += rb; + } +} + +static int getShift(SkBitmap::Config config) { + switch (config) { + case SkBitmap::kARGB_8888_Config: + return 2; + case SkBitmap::kRGB_565_Config: + case SkBitmap::kARGB_4444_Config: + return 1; + case SkBitmap::kIndex8_Config: + case SkBitmap::kA8_Config: + return 0; + default: + return -1; // signal not supported + } +} + +void SkFlipPixelRef::CopyBitsFromAddr(const SkBitmap& dst, const SkRegion& clip, + const void* srcAddr) { + const int shift = getShift(dst.config()); + if (shift < 0) { + return; + } + + const SkIRect bounds = {0, 0, dst.width(), dst.height()}; + SkRegion::Cliperator iter(clip, bounds); + + while (!iter.done()) { + copyRect(dst, iter.rect(), srcAddr, shift); + iter.next(); + } +} + diff --git a/src/images/SkImageDecoder.cpp b/src/images/SkImageDecoder.cpp new file mode 100644 index 0000000000..18b52d689c --- /dev/null +++ b/src/images/SkImageDecoder.cpp @@ -0,0 +1,190 @@ +/* libs/graphics/images/SkImageDecoder.cpp +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "SkImageDecoder.h" +#include "SkBitmap.h" +#include "SkPixelRef.h" +#include "SkStream.h" +#include "SkTemplates.h" + +static SkBitmap::Config gDeviceConfig = SkBitmap::kNo_Config; + +SkBitmap::Config SkImageDecoder::GetDeviceConfig() +{ + return gDeviceConfig; +} + +void SkImageDecoder::SetDeviceConfig(SkBitmap::Config config) +{ + gDeviceConfig = config; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkImageDecoder::SkImageDecoder() + : fPeeker(NULL), fChooser(NULL), fAllocator(NULL), fSampleSize(1), + fDitherImage(true) { +} + +SkImageDecoder::~SkImageDecoder() { + fPeeker->safeUnref(); + fChooser->safeUnref(); + fAllocator->safeUnref(); +} + +SkImageDecoder::Format SkImageDecoder::getFormat() const { + return kUnknown_Format; +} + +SkImageDecoder::Peeker* SkImageDecoder::setPeeker(Peeker* peeker) { + SkRefCnt_SafeAssign(fPeeker, peeker); + return peeker; +} + +SkImageDecoder::Chooser* SkImageDecoder::setChooser(Chooser* chooser) { + SkRefCnt_SafeAssign(fChooser, chooser); + return chooser; +} + +SkBitmap::Allocator* SkImageDecoder::setAllocator(SkBitmap::Allocator* alloc) { + SkRefCnt_SafeAssign(fAllocator, alloc); + return alloc; +} + +void SkImageDecoder::setSampleSize(int size) { + if (size < 1) { + size = 1; + } + fSampleSize = size; +} + +bool SkImageDecoder::chooseFromOneChoice(SkBitmap::Config config, int width, + int height) const { + Chooser* chooser = fChooser; + + if (NULL == chooser) { // no chooser, we just say YES to decoding :) + return true; + } + chooser->begin(1); + chooser->inspect(0, config, width, height); + return chooser->choose() == 0; +} + +bool SkImageDecoder::allocPixelRef(SkBitmap* bitmap, + SkColorTable* ctable) const { + return bitmap->allocPixels(fAllocator, ctable); +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkImageDecoder::decode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config pref, Mode mode) { + SkBitmap tmp; + + // we reset this to false before calling onDecode + fShouldCancelDecode = false; + + // pass a temporary bitmap, so that if we return false, we are assured of + // leaving the caller's bitmap untouched. + if (this->onDecode(stream, &tmp, pref, mode)) { + /* We operate on a tmp bitmap until we know we succeed. This way + we're sure we don't change the caller's bitmap and then later + return false. Returning false must mean that their parameter + is unchanged. + */ + bm->swap(tmp); + return true; + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkImageDecoder::DecodeFile(const char file[], SkBitmap* bm, + SkBitmap::Config pref, Mode mode) { + SkASSERT(file); + SkASSERT(bm); + + SkFILEStream stream(file); + if (stream.isValid()) { + if (SkImageDecoder::DecodeStream(&stream, bm, pref, mode)) { + bm->pixelRef()->setURI(file); + } + return true; + } + return false; +} + +bool SkImageDecoder::DecodeMemory(const void* buffer, size_t size, SkBitmap* bm, + SkBitmap::Config pref, Mode mode) { + if (0 == size) { + return false; + } + SkASSERT(buffer); + + SkMemoryStream stream(buffer, size); + return SkImageDecoder::DecodeStream(&stream, bm, pref, mode); +} + +bool SkImageDecoder::DecodeStream(SkStream* stream, SkBitmap* bm, + SkBitmap::Config pref, Mode mode) { + SkASSERT(stream); + SkASSERT(bm); + + bool success = false; + SkImageDecoder* codec = SkImageDecoder::Factory(stream); + + if (NULL != codec) { + success = codec->decode(stream, bm, pref, mode); + delete codec; + } + return success; +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_SUPPORT_IMAGE_ENCODE + +SkImageEncoder::~SkImageEncoder() {} + +bool SkImageEncoder::encodeStream(SkWStream* stream, const SkBitmap& bm, + int quality) { + quality = SkMin32(100, SkMax32(0, quality)); + return this->onEncode(stream, bm, quality); +} + +bool SkImageEncoder::encodeFile(const char file[], const SkBitmap& bm, + int quality) { + quality = SkMin32(100, SkMax32(0, quality)); + SkFILEWStream stream(file); + return this->onEncode(&stream, bm, quality); +} + +bool SkImageEncoder::EncodeFile(const char file[], const SkBitmap& bm, Type t, + int quality) { + SkAutoTDelete<SkImageEncoder> enc(SkImageEncoder::Create(t)); + return enc.get() && enc.get()->encodeFile(file, bm, quality); +} + +bool SkImageEncoder::EncodeStream(SkWStream* stream, const SkBitmap& bm, Type t, + int quality) { + SkAutoTDelete<SkImageEncoder> enc(SkImageEncoder::Create(t)); + return enc.get() && enc.get()->encodeStream(stream, bm, quality); +} + +#endif + diff --git a/src/images/SkImageDecoder_fpdfemb.cpp b/src/images/SkImageDecoder_fpdfemb.cpp new file mode 100644 index 0000000000..7f37e3db02 --- /dev/null +++ b/src/images/SkImageDecoder_fpdfemb.cpp @@ -0,0 +1,236 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkImageDecoder.h" +#include "SkScaledBitmapSampler.h" +#include "SkStream.h" +#include "SkColorPriv.h" +#include "SkTDArray.h" + +#include "fpdfemb.h" + +class SkFPDFEMBImageDecoder : public SkImageDecoder { +public: + SkFPDFEMBImageDecoder() {} + + virtual Format getFormat() const { + return kBMP_Format; + } + +protected: + virtual bool onDecode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config pref, Mode mode); + +private: + bool render(FPDFEMB_PAGE page, const FPDFEMB_RECT& bounds, SkBitmap* bm, + SkBitmap::Config prefConfig, SkImageDecoder::Mode mode); +}; + +SkImageDecoder* SkImageDecoder_FPDFEMB_Factory(SkStream*); +SkImageDecoder* SkImageDecoder_FPDFEMB_Factory(SkStream* stream) { + static const char kPDFSig[] = { '%', 'P', 'D', 'F' }; + + size_t len = stream->getLength(); + char buffer[sizeof(kPDFSig)]; + + SkDebugf("---- SkImageDecoder_FPDFEMB_Factory len=%d\n", len); + + if (len != 12683) { return NULL; } + + if (len > sizeof(kPDFSig) && + stream->read(buffer, sizeof(kPDFSig)) == sizeof(kPDFSig) && + !memcmp(buffer, kPDFSig, sizeof(kPDFSig))) { + return SkNEW(SkFPDFEMBImageDecoder); + } + return NULL; +} + +/////////////////////////////////////////////////////////////////////////////// + +extern "C" { + static void* pdf_alloc(FPDFEMB_MEMMGR* pMgr, unsigned int size) { + void* addr = sk_malloc_throw(size); + // SkDebugf("---- pdf_alloc %d %p\n", size, addr); + return addr; + } + + static void* pdf_alloc_nl(FPDFEMB_MEMMGR* pMgr, unsigned int size) { + void* addr = sk_malloc_flags(size, 0); + // SkDebugf("---- pdf_alloc_nl %d %p\n", size, addr); + return addr; + } + + static void* pdf_realloc(FPDFEMB_MEMMGR*, void* addr, unsigned int size) { + void* newaddr = sk_realloc_throw(addr, size); + // SkDebugf("---- pdf_realloc %p %d %p\n", addr, size, newaddr); + return newaddr; + } + + static void pdf_free(FPDFEMB_MEMMGR* pMgr, void* pointer) { + // SkDebugf("---- pdf_free %p\n", pointer); + sk_free(pointer); + } + + void FX_OUTPUT_LOG_FUNC(const char* format, ...) { + SkDebugf("---- LOG_FUNC %s\n", format); + } + + static unsigned int file_getsize(FPDFEMB_FILE_ACCESS* file) { + SkStream* stream = (SkStream*)file->user; + return stream->getLength(); + } + + static FPDFEMB_RESULT file_readblock(FPDFEMB_FILE_ACCESS* file, void* dst, + unsigned int offset, unsigned int size) { + SkStream* stream = (SkStream*)file->user; +// SkDebugf("---- readblock %p %p %d %d\n", stream, dst, offset, size); + if (!stream->rewind()) { + SkDebugf("---- rewind failed\n"); + return FPDFERR_ERROR; + } + if (stream->skip(offset) != offset) { + SkDebugf("---- skip failed\n"); + return FPDFERR_ERROR; + } + if (stream->read(dst, size) != size) { + SkDebugf("---- read failed\n"); + return FPDFERR_ERROR; + } + return FPDFERR_SUCCESS; + } + + static void pdf_oom_handler(void* memory, int size) { + SkDebugf("======== pdf OOM %p %d\n", memory, size); + } +} + +static inline int PDF2Pixels(int x) { return x / 100; } +static inline SkScalar PDF2Scalar(int x) { + return SkScalarMulDiv(SK_Scalar1, x, 100); +} + +bool SkFPDFEMBImageDecoder::render(FPDFEMB_PAGE page, const FPDFEMB_RECT& bounds, SkBitmap* bm, + SkBitmap::Config prefConfig, SkImageDecoder::Mode mode) { + int width = PDF2Pixels(bounds.right - bounds.left); + int height = PDF2Pixels(bounds.top - bounds.bottom); + + SkDebugf("----- bitmap size [%d %d], mode=%d\n", width, height, mode); + bm->setConfig(SkBitmap::kARGB_8888_Config, width, height); + if (SkImageDecoder::kDecodeBounds_Mode == mode) { + return true; + } + + // USE THE CODEC TO ALLOCATE THE PIXELS!!!! + if (!this->allocPixelRef(bm, NULL)) { + SkDebugf("----- failed to alloc pixels\n"); + return false; + } + + bm->eraseColor(0); + + FPDFEMB_RESULT result; + FPDFEMB_BITMAP dib; + + result = FPDFEMB_CreateDIB(width, height, FPDFDIB_BGRA, bm->getPixels(), + bm->rowBytes(), &dib); + SkDebugf("---- createdib %d\n", result); + + result = FPDFEMB_StartRender(dib, page, 0, 0, width, height, 0, 0, NULL, NULL); + SkDebugf("---- render %d\n", result); + + result = FPDFEMB_DestroyDIB(dib); + SkDebugf("---- destroydib %d\n", result); + + SkPMColor* dst = bm->getAddr32(0, 0); + const uint8_t* src = (uint8_t*)dst; + int n = bm->getSize() >> 2; + for (int i = 0; i < n; i++) { + int b = *src++; + int g = *src++; + int r = *src++; + int a = *src++; + *dst++ = SkPackARGB32(a, r, g, b); + } + + return true; +} + +#define USE_FIXED_MEM (4 * 1024 * 1024) + +bool SkFPDFEMBImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config prefConfig, Mode mode) { + + FPDFEMB_RESULT result; +#ifdef USE_FIXED_MEM + SkAutoMalloc storage(USE_FIXED_MEM); + result = FPDFEMB_InitFixedMemory(storage.get(), USE_FIXED_MEM, + pdf_oom_handler); +#else + FPDFEMB_MEMMGR memmgr; + memmgr.Alloc = pdf_alloc; + memmgr.AllocNL = pdf_alloc_nl; + memmgr.Realloc = pdf_realloc; + memmgr.Free = pdf_free; + + result = FPDFEMB_Init(&memmgr); +#endif + SkDebugf("----- SkImageDecoder_FPDFEMB_Factory init %d, streamLen = %d\n", result, stream->getLength()); + + FPDFEMB_FILE_ACCESS file; + file.GetSize = file_getsize; + file.ReadBlock = file_readblock; + file.user = stream; + + FPDFEMB_DOCUMENT document; + result = FPDFEMB_StartLoadDocument(&file, NULL, &document, NULL); + SkDebugf("----- SkImageDecoder_FPDFEMB_Factory open %d %p\n", result, document); + + int pageCount = FPDFEMB_GetPageCount(document); + SkDebugf("----- SkImageDecoder_FPDFEMB_Factory pageCount %d\n", pageCount); + + if (pageCount > 0) { + FPDFEMB_PAGE page; + result = FPDFEMB_LoadPage(document, 0, &page); + SkDebugf("----- SkImageDecoder_FPDFEMB_Factory load page %d\n", result); + + int width, height; + result = FPDFEMB_GetPageSize(page, &width, &height); + SkDebugf("----- SkImageDecoder_FPDFEMB_Factory page size %d [%d %d]\n", result, width, height); + + FPDFEMB_RECT rect; + result = FPDFEMB_GetPageBBox(page, &rect); + SkDebugf("----- SkImageDecoder_FPDFEMB_Factory page rect %d [%d %d %d %d]\n", result, + rect.left, rect.top, rect.right, rect.bottom); + + SkDebugf("----- SkImageDecoder_FPDFEMB_Factory begin page parse...\n"); + result = FPDFEMB_StartParse(page, false, NULL); + SkDebugf("----- SkImageDecoder_FPDFEMB_Factory page parse %d\n", result); + + if (0 == result) { + this->render(page, rect, bm, prefConfig, mode); + } + + result = FPDFEMB_ClosePage(page); + SkDebugf("----- SkImageDecoder_FPDFEMB_Factory close page %d\n", result); + } + + result = FPDFEMB_CloseDocument(document); + SkDebugf("----- SkImageDecoder_FPDFEMB_Factory close %d\n", result); + + // FPDFEMB_Exit(); + + return true; +} diff --git a/src/images/SkImageDecoder_libbmp.cpp b/src/images/SkImageDecoder_libbmp.cpp new file mode 100644 index 0000000000..32a7a6de42 --- /dev/null +++ b/src/images/SkImageDecoder_libbmp.cpp @@ -0,0 +1,147 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bmpdecoderhelper.h" +#include "SkImageDecoder.h" +#include "SkScaledBitmapSampler.h" +#include "SkStream.h" +#include "SkColorPriv.h" +#include "SkTDArray.h" + +class SkBMPImageDecoder : public SkImageDecoder { +public: + SkBMPImageDecoder() {} + + virtual Format getFormat() const { + return kBMP_Format; + } + +protected: + virtual bool onDecode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config pref, Mode mode); +}; + +SkImageDecoder* SkImageDecoder_BMP_Factory(SkStream*); +SkImageDecoder* SkImageDecoder_BMP_Factory(SkStream* stream) { + static const char kBmpMagic[] = { 'B', 'M' }; + + size_t len = stream->getLength(); + char buffer[sizeof(kBmpMagic)]; + + if (len > sizeof(kBmpMagic) && + stream->read(buffer, sizeof(kBmpMagic)) == sizeof(kBmpMagic) && + !memcmp(buffer, kBmpMagic, sizeof(kBmpMagic))) { + return SkNEW(SkBMPImageDecoder); + } + return NULL; +} + +/////////////////////////////////////////////////////////////////////////////// + +class SkBmpDecoderCallback : public image_codec::BmpDecoderCallback { +public: + // we don't copy the bitmap, just remember the pointer + SkBmpDecoderCallback(bool justBounds) : fJustBounds(justBounds) {} + + // override from BmpDecoderCallback + virtual uint8* SetSize(int width, int height) { + fWidth = width; + fHeight = height; + if (fJustBounds) { + return NULL; + } + + fRGB.setCount(width * height * 3); // 3 == r, g, b + return fRGB.begin(); + } + + int width() const { return fWidth; } + int height() const { return fHeight; } + uint8_t* rgb() const { return fRGB.begin(); } + +private: + SkTDArray<uint8_t> fRGB; + int fWidth; + int fHeight; + bool fJustBounds; +}; + +bool SkBMPImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config prefConfig, Mode mode) { + + size_t length = stream->getLength(); + SkAutoMalloc storage(length); + + if (stream->read(storage.get(), length) != length) { + return false; + } + + const bool justBounds = SkImageDecoder::kDecodeBounds_Mode == mode; + SkBmpDecoderCallback callback(justBounds); + + // Now decode the BMP into callback's rgb() array [r,g,b, r,g,b, ...] + { + image_codec::BmpDecoderHelper helper; + const int max_pixels = 16383*16383; // max width*height + if (!helper.DecodeImage((const char*)storage.get(), length, + max_pixels, &callback)) { + return false; + } + } + + // we don't need this anymore, so free it now (before we try to allocate + // the bitmap's pixels) rather than waiting for its destructor + storage.free(); + + int width = callback.width(); + int height = callback.height(); + SkBitmap::Config config = SkBitmap::kARGB_8888_Config; + + // only accept prefConfig if it makes sense for us + if (SkBitmap::kARGB_4444_Config == prefConfig || + SkBitmap::kRGB_565_Config == config) { + config = prefConfig; + } + + SkScaledBitmapSampler sampler(width, height, getSampleSize()); + + bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight()); + bm->setIsOpaque(true); + if (justBounds) { + return true; + } + + if (!this->allocPixelRef(bm, NULL)) { + return false; + } + + SkAutoLockPixels alp(*bm); + + if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, getDitherImage())) { + return false; + } + + const int srcRowBytes = width * 3; + const int dstHeight = sampler.scaledHeight(); + const uint8_t* srcRow = callback.rgb(); + + srcRow += sampler.srcY0() * srcRowBytes; + for (int y = 0; y < dstHeight; y++) { + sampler.next(srcRow); + srcRow += sampler.srcDY() * srcRowBytes; + } + return true; +} diff --git a/src/images/SkImageDecoder_libgif.cpp b/src/images/SkImageDecoder_libgif.cpp new file mode 100644 index 0000000000..519366a18e --- /dev/null +++ b/src/images/SkImageDecoder_libgif.cpp @@ -0,0 +1,340 @@ +/* libs/graphics/images/SkImageDecoder_libgif.cpp +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "SkImageDecoder.h" +#include "SkColor.h" +#include "SkColorPriv.h" +#include "SkStream.h" +#include "SkTemplates.h" +#include "SkPackBits.h" + +#include "gif_lib.h" + +class SkGIFImageDecoder : public SkImageDecoder { +public: + virtual Format getFormat() const { + return kGIF_Format; + } + +protected: + virtual bool onDecode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config pref, Mode mode); +}; + +static const uint8_t gStartingIterlaceYValue[] = { + 0, 4, 2, 1 +}; +static const uint8_t gDeltaIterlaceYValue[] = { + 8, 8, 4, 2 +}; + +/* Implement the GIF interlace algorithm in an iterator. + 1) grab every 8th line beginning at 0 + 2) grab every 8th line beginning at 4 + 3) grab every 4th line beginning at 2 + 4) grab every 2nd line beginning at 1 +*/ +class GifInterlaceIter { +public: + GifInterlaceIter(int height) : fHeight(height) { + fStartYPtr = gStartingIterlaceYValue; + fDeltaYPtr = gDeltaIterlaceYValue; + + fCurrY = *fStartYPtr++; + fDeltaY = *fDeltaYPtr++; + } + + int currY() const { + SkASSERT(fStartYPtr); + SkASSERT(fDeltaYPtr); + return fCurrY; + } + + void next() { + SkASSERT(fStartYPtr); + SkASSERT(fDeltaYPtr); + + int y = fCurrY + fDeltaY; + // We went from an if statement to a while loop so that we iterate + // through fStartYPtr until a valid row is found. This is so that images + // that are smaller than 5x5 will not trash memory. + while (y >= fHeight) { + if (gStartingIterlaceYValue + + SK_ARRAY_COUNT(gStartingIterlaceYValue) == fStartYPtr) { + // we done + SkDEBUGCODE(fStartYPtr = NULL;) + SkDEBUGCODE(fDeltaYPtr = NULL;) + y = 0; + } else { + y = *fStartYPtr++; + fDeltaY = *fDeltaYPtr++; + } + } + fCurrY = y; + } + +private: + const int fHeight; + int fCurrY; + int fDeltaY; + const uint8_t* fStartYPtr; + const uint8_t* fDeltaYPtr; +}; + +/////////////////////////////////////////////////////////////////////////////// + +//#define GIF_STAMP "GIF" /* First chars in file - GIF stamp. */ +//#define GIF_STAMP_LEN (sizeof(GIF_STAMP) - 1) + +static int DecodeCallBackProc(GifFileType* fileType, GifByteType* out, + int size) { + SkStream* stream = (SkStream*) fileType->UserData; + return (int) stream->read(out, size); +} + +void CheckFreeExtension(SavedImage* Image) { + if (Image->ExtensionBlocks) { + FreeExtension(Image); + } +} + +// return NULL on failure +static const ColorMapObject* find_colormap(const GifFileType* gif) { + const ColorMapObject* cmap = gif->Image.ColorMap; + if (NULL == cmap) { + cmap = gif->SColorMap; + } + // some sanity checks + if ((unsigned)cmap->ColorCount > 256 || + cmap->ColorCount != (1 << cmap->BitsPerPixel)) { + cmap = NULL; + } + return cmap; +} + +// return -1 if not found (i.e. we're completely opaque) +static int find_transpIndex(const SavedImage& image, int colorCount) { + int transpIndex = -1; + for (int i = 0; i < image.ExtensionBlockCount; ++i) { + const ExtensionBlock* eb = image.ExtensionBlocks + i; + if (eb->Function == 0xF9 && eb->ByteCount == 4) { + if (eb->Bytes[0] & 1) { + transpIndex = (unsigned char)eb->Bytes[3]; + // check for valid transpIndex + if (transpIndex >= colorCount) { + transpIndex = -1; + } + break; + } + } + } + return transpIndex; +} + +static bool error_return(GifFileType* gif, const SkBitmap& bm, + const char msg[]) { +#if 0 + SkDebugf("libgif error <%s> bitmap [%d %d] pixels %p colortable %p\n", + msg, bm.width(), bm.height(), bm.getPixels(), bm.getColorTable()); +#endif + return false; +} + +bool SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, + SkBitmap::Config prefConfig, Mode mode) { + GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc); + if (NULL == gif) { + return error_return(gif, *bm, "DGifOpen"); + } + + SkAutoTCallIProc<GifFileType, DGifCloseFile> acp(gif); + + SavedImage temp_save; + temp_save.ExtensionBlocks=NULL; + temp_save.ExtensionBlockCount=0; + SkAutoTCallVProc<SavedImage, CheckFreeExtension> acp2(&temp_save); + + int width, height; + GifRecordType recType; + GifByteType *extData; + + do { + if (DGifGetRecordType(gif, &recType) == GIF_ERROR) { + return error_return(gif, *bm, "DGifGetRecordType"); + } + + switch (recType) { + case IMAGE_DESC_RECORD_TYPE: { + if (DGifGetImageDesc(gif) == GIF_ERROR) { + return error_return(gif, *bm, "IMAGE_DESC_RECORD_TYPE"); + } + + if (gif->ImageCount < 1) { // sanity check + return error_return(gif, *bm, "ImageCount < 1"); + } + + width = gif->SWidth; + height = gif->SHeight; + if (width <= 0 || height <= 0 || + !this->chooseFromOneChoice(SkBitmap::kIndex8_Config, + width, height)) { + return error_return(gif, *bm, "chooseFromOneChoice"); + } + + bm->setConfig(SkBitmap::kIndex8_Config, width, height); + if (SkImageDecoder::kDecodeBounds_Mode == mode) + return true; + + SavedImage* image = &gif->SavedImages[gif->ImageCount-1]; + const GifImageDesc& desc = image->ImageDesc; + + // check for valid descriptor + if ( (desc.Top | desc.Left) < 0 || + desc.Left + desc.Width > width || + desc.Top + desc.Height > height) { + return error_return(gif, *bm, "TopLeft"); + } + + // now we decode the colortable + int colorCount = 0; + { + const ColorMapObject* cmap = find_colormap(gif); + if (NULL == cmap) { + return error_return(gif, *bm, "null cmap"); + } + + colorCount = cmap->ColorCount; + SkColorTable* ctable = SkNEW_ARGS(SkColorTable, (colorCount)); + SkPMColor* colorPtr = ctable->lockColors(); + for (int index = 0; index < colorCount; index++) + colorPtr[index] = SkPackARGB32(0xFF, + cmap->Colors[index].Red, + cmap->Colors[index].Green, + cmap->Colors[index].Blue); + + int transpIndex = find_transpIndex(temp_save, colorCount); + if (transpIndex < 0) + ctable->setFlags(ctable->getFlags() | SkColorTable::kColorsAreOpaque_Flag); + else + colorPtr[transpIndex] = 0; // ram in a transparent SkPMColor + ctable->unlockColors(true); + + SkAutoUnref aurts(ctable); + if (!this->allocPixelRef(bm, ctable)) { + return error_return(gif, *bm, "allocPixelRef"); + } + } + + SkAutoLockPixels alp(*bm); + + // time to decode the scanlines + // + uint8_t* scanline = bm->getAddr8(0, 0); + const int rowBytes = bm->rowBytes(); + const int innerWidth = desc.Width; + const int innerHeight = desc.Height; + + // abort if either inner dimension is <= 0 + if (innerWidth <= 0 || innerHeight <= 0) { + return error_return(gif, *bm, "non-pos inner width/height"); + } + + // are we only a subset of the total bounds? + if ((desc.Top | desc.Left) > 0 || + innerWidth < width || innerHeight < height) + { + uint8_t fill = (uint8_t)gif->SBackGroundColor; + // check for valid fill index/color + if (fill >= (unsigned)colorCount) { + fill = 0; + } + memset(scanline, gif->SBackGroundColor, bm->getSize()); + // bump our starting address + scanline += desc.Top * rowBytes + desc.Left; + } + + // now decode each scanline + if (gif->Image.Interlace) + { + GifInterlaceIter iter(innerHeight); + for (int y = 0; y < innerHeight; y++) + { + uint8_t* row = scanline + iter.currY() * rowBytes; + if (DGifGetLine(gif, row, innerWidth) == GIF_ERROR) { + return error_return(gif, *bm, "interlace DGifGetLine"); + } + iter.next(); + } + } + else + { + // easy, non-interlace case + for (int y = 0; y < innerHeight; y++) { + if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) { + return error_return(gif, *bm, "DGifGetLine"); + } + scanline += rowBytes; + } + } + goto DONE; + } break; + + case EXTENSION_RECORD_TYPE: + if (DGifGetExtension(gif, &temp_save.Function, + &extData) == GIF_ERROR) { + return error_return(gif, *bm, "DGifGetExtension"); + } + + while (extData != NULL) { + /* Create an extension block with our data */ + if (AddExtensionBlock(&temp_save, extData[0], + &extData[1]) == GIF_ERROR) { + return error_return(gif, *bm, "AddExtensionBlock"); + } + if (DGifGetExtensionNext(gif, &extData) == GIF_ERROR) { + return error_return(gif, *bm, "DGifGetExtensionNext"); + } + temp_save.Function = 0; + } + break; + + case TERMINATE_RECORD_TYPE: + break; + + default: /* Should be trapped by DGifGetRecordType */ + break; + } + } while (recType != TERMINATE_RECORD_TYPE); + +DONE: + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkImageDecoder* SkImageDecoder_GIF_Factory(SkStream* stream) { + char buf[GIF_STAMP_LEN]; + if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) { + if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 || + memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 || + memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) { + return SkNEW(SkGIFImageDecoder); + } + } + return NULL; +} + diff --git a/src/images/SkImageDecoder_libico.cpp b/src/images/SkImageDecoder_libico.cpp new file mode 100644 index 0000000000..b179a6b61a --- /dev/null +++ b/src/images/SkImageDecoder_libico.cpp @@ -0,0 +1,388 @@ +/* libs/graphics/images/SkImageDecoder_libico.cpp +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "SkImageDecoder.h" +#include "SkStream.h" +#include "SkColorPriv.h" +#include "SkTypes.h" + +class SkICOImageDecoder : public SkImageDecoder { +public: + SkICOImageDecoder(); + + virtual Format getFormat() const { + return kICO_Format; + } + +protected: + virtual bool onDecode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config pref, Mode); +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +//read bytes starting from the begin-th index in the buffer +//read in Intel order, and return an integer + +#define readByte(buffer,begin) buffer[begin] +#define read2Bytes(buffer,begin) buffer[begin]+(buffer[begin+1]<<8) +#define read4Bytes(buffer,begin) buffer[begin]+(buffer[begin+1]<<8)+(buffer[begin+2]<<16)+(buffer[begin+3]<<24) + +///////////////////////////////////////////////////////////////////////////////////////// + +SkImageDecoder* SkImageDecoder_ICO_Factory(SkStream*); +SkImageDecoder* SkImageDecoder_ICO_Factory(SkStream* stream) +{ + //i'm going to check if we basically have 0,0,1,0 (reserved = 0, type = 1) + //is that required and sufficient? + SkAutoMalloc autoMal(4); + unsigned char* buf = (unsigned char*)autoMal.get(); + stream->read((void*)buf, 4); + int reserved = read2Bytes(buf, 0); + int type = read2Bytes(buf, 2); + if (reserved != 0 || type != 1) //it's not an ico + return NULL; + return SkNEW(SkICOImageDecoder); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +SkICOImageDecoder::SkICOImageDecoder() +{ +} + +//helpers - my function pointer will call one of these, depending on the bitCount, each time through the inner loop +static void editPixelBit1(const int pixelNo, const unsigned char* buf, + const int xorOffset, int& x, int y, const int w, + SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors); +static void editPixelBit4(const int pixelNo, const unsigned char* buf, + const int xorOffset, int& x, int y, const int w, + SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors); +static void editPixelBit8(const int pixelNo, const unsigned char* buf, + const int xorOffset, int& x, int y, const int w, + SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors); +static void editPixelBit24(const int pixelNo, const unsigned char* buf, + const int xorOffset, int& x, int y, const int w, + SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors); +static void editPixelBit32(const int pixelNo, const unsigned char* buf, + const int xorOffset, int& x, int y, const int w, + SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors); + + +static int calculateRowBytesFor8888(int w, int bitCount) +{ + // Default rowBytes is w << 2 for kARGB_8888 + // In the case of a 4 bit image with an odd width, we need to add some + // so we can go off the end of the drawn bitmap. + // Add 4 to ensure that it is still a multiple of 4. + if (4 == bitCount && (w & 0x1)) { + return (w + 1) << 2; + } + // Otherwise return 0, which will allow it to be calculated automatically. + return 0; +} + +bool SkICOImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config pref, Mode mode) +{ + size_t length = stream->read(NULL, 0); + SkAutoMalloc autoMal(length); + unsigned char* buf = (unsigned char*)autoMal.get(); + if (stream->read((void*)buf, length) != length) { + return false; + } + + //these should always be the same - should i use for error checking? - what about files that have some + //incorrect values, but still decode properly? + int reserved = read2Bytes(buf, 0); // 0 + int type = read2Bytes(buf, 2); // 1 + if (reserved != 0 || type != 1) + return false; + int count = read2Bytes(buf, 4); + + //need to at least have enough space to hold the initial table of info + if (length < (size_t)(6 + count*16)) + return false; + + int choice; + Chooser* chooser = this->getChooser(); + //FIXME:if no chooser, consider providing the largest color image + //what are the odds that the largest image would be monochrome? + if (NULL == chooser) { + choice = 0; + } else { + chooser->begin(count); + for (int i = 0; i < count; i++) + { + //need to find out the config, width, and height from the stream + int width = readByte(buf, 6 + i*16); + int height = readByte(buf, 7 + i*16); + int offset = read4Bytes(buf, 18 + i*16); + int bitCount = read2Bytes(buf, offset+14); + SkBitmap::Config c; + //currently only provide ARGB_8888_, but maybe we want kIndex8_Config for 1 and 4, and possibly 8? + //or maybe we'll determine this based on the provided config + switch (bitCount) + { + case 1: + case 4: + // In reality, at least for the moment, these will be decoded into kARGB_8888 bitmaps. + // However, this will be used to distinguish between the lower quality 1bpp and 4 bpp + // images and the higher quality images. + c = SkBitmap::kIndex8_Config; + break; + case 8: + case 24: + case 32: + c = SkBitmap::kARGB_8888_Config; + break; + default: + SkDEBUGF(("Image with %ibpp not supported\n", bitCount)); + continue; + } + chooser->inspect(i, c, width, height); + } + choice = chooser->choose(); + } + + //you never know what the chooser is going to supply + if (choice >= count || choice < 0) + return false; + + //skip ahead to the correct header + //commented out lines are not used, but if i switch to other read method, need to know how many to skip + //otherwise, they could be used for error checking + int w = readByte(buf, 6 + choice*16); + int h = readByte(buf, 7 + choice*16); + int colorCount = readByte(buf, 8 + choice*16); + //int reservedToo = readByte(buf, 9 + choice*16); //0 + //int planes = read2Bytes(buf, 10 + choice*16); //1 - but often 0 + //int fakeBitCount = read2Bytes(buf, 12 + choice*16); //should be real - usually 0 + int size = read4Bytes(buf, 14 + choice*16); //matters? + int offset = read4Bytes(buf, 18 + choice*16); + if ((size_t)(offset + size) > length) + return false; + //int infoSize = read4Bytes(buf, offset); //40 + //int width = read4Bytes(buf, offset+4); //should == w + //int height = read4Bytes(buf, offset+8); //should == 2*h + //int planesToo = read2Bytes(buf, offset+12); //should == 1 (does it?) + int bitCount = read2Bytes(buf, offset+14); + + void (*placePixel)(const int pixelNo, const unsigned char* buf, + const int xorOffset, int& x, int y, const int w, + SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors) = NULL; + switch (bitCount) + { + case 1: + placePixel = &editPixelBit1; + colorCount = 2; + break; + case 4: + placePixel = &editPixelBit4; + colorCount = 16; + break; + case 8: + placePixel = &editPixelBit8; + colorCount = 256; + break; + case 24: + placePixel = &editPixelBit24; + colorCount = 0; + break; + case 32: + placePixel = &editPixelBit32; + colorCount = 0; + break; + default: + SkDEBUGF(("Decoding %ibpp is unimplemented\n", bitCount)); + return false; + } + + //these should all be zero, but perhaps are not - need to check + //int compression = read4Bytes(buf, offset+16); //0 + //int imageSize = read4Bytes(buf, offset+20); //0 - sometimes has a value + //int xPixels = read4Bytes(buf, offset+24); //0 + //int yPixels = read4Bytes(buf, offset+28); //0 + //int colorsUsed = read4Bytes(buf, offset+32) //0 - might have an actual value though + //int colorsImportant = read4Bytes(buf, offset+36); //0 + + int begin = offset + 40; + //this array represents the colortable + //if i allow other types of bitmaps, it may actually be used as a part of the bitmap + SkPMColor* colors = NULL; + int blue, green, red; + if (colorCount) + { + colors = new SkPMColor[colorCount]; + for (int j = 0; j < colorCount; j++) + { + //should this be a function - maybe a #define? + blue = readByte(buf, begin + 4*j); + green = readByte(buf, begin + 4*j + 1); + red = readByte(buf, begin + 4*j + 2); + colors[j] = SkPackARGB32(0xFF, red & 0xFF, green & 0xFF, blue & 0xFF); + } + } + int bitWidth = w*bitCount; + int test = bitWidth & 0x1F; + int mask = -(((test >> 4) | (test >> 3) | (test >> 2) | (test >> 1) | test) & 0x1); //either 0xFFFFFFFF or 0 + int lineBitWidth = (bitWidth & 0xFFFFFFE0) + (0x20 & mask); + int lineWidth = lineBitWidth/bitCount; + + int xorOffset = begin + colorCount*4; //beginning of the color bitmap + //other read method means we will just be here already + int andOffset = xorOffset + ((lineWidth*h*bitCount) >> 3); + + /*int */test = w & 0x1F; //the low 5 bits - we are rounding up to the next 32 (2^5) + /*int */mask = -(((test >> 4) | (test >> 3) | (test >> 2) | (test >> 1) | test) & 0x1); //either 0xFFFFFFFF or 0 + int andLineWidth = (w & 0xFFFFFFE0) + (0x20 & mask); + //if we allow different Configs, everything is the same til here + //change the config, and use different address getter, and place index vs color, and add the color table + //FIXME: what is the tradeoff in size? + //if the andbitmap (mask) is all zeroes, then we can easily do an index bitmap + //however, with small images with large colortables, maybe it's better to still do argb_8888 + + bm->setConfig(SkBitmap::kARGB_8888_Config, w, h, calculateRowBytesFor8888(w, bitCount)); + + if (SkImageDecoder::kDecodeBounds_Mode == mode) { + delete[] colors; + return true; + } + + if (!this->allocPixelRef(bm, NULL)) + { + delete[] colors; + return false; + } + + SkAutoLockPixels alp(*bm); + + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + //U32* address = bm->getAddr32(x, y); + + //check the alpha bit first, but pass it along to the function to figure out how to deal with it + int andPixelNo = andLineWidth*(h-y-1)+x; + //only need to get a new alphaByte when x %8 == 0 + //but that introduces an if and a mod - probably much slower + //that's ok, it's just a read of an array, not a stream + int alphaByte = readByte(buf, andOffset + (andPixelNo >> 3)); + int shift = 7 - (andPixelNo & 0x7); + int m = 1 << shift; + + int pixelNo = lineWidth*(h-y-1)+x; + placePixel(pixelNo, buf, xorOffset, x, y, w, bm, alphaByte, m, shift, colors); + + } + } + + delete [] colors; + //ensure we haven't read off the end? + //of course this doesn't help us if the andOffset was a lie... + //return andOffset + (andLineWidth >> 3) <= length; + return true; +} //onDecode + +//function to place the pixel, determined by the bitCount +static void editPixelBit1(const int pixelNo, const unsigned char* buf, + const int xorOffset, int& x, int y, const int w, + SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors) +{ + // note that this should be the same as/similar to the AND bitmap + SkPMColor* address = bm->getAddr32(x,y); + int byte = readByte(buf, xorOffset + (pixelNo >> 3)); + int colorBit; + int alphaBit; + // Read all of the bits in this byte. + int i = x + 8; + // Pin to the width so we do not write outside the bounds of + // our color table. + i = i > w ? w : i; + // While loop to check all 8 bits individually. + while (x < i) + { + + colorBit = (byte & m) >> shift; + alphaBit = (alphaByte & m) >> shift; + *address = (alphaBit-1)&(colors[colorBit]); + x++; + // setup for the next pixel + address = address + 1; + m = m >> 1; + shift -= 1; + } + x--; +} +static void editPixelBit4(const int pixelNo, const unsigned char* buf, + const int xorOffset, int& x, int y, const int w, + SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors) +{ + SkPMColor* address = bm->getAddr32(x, y); + int byte = readByte(buf, xorOffset + (pixelNo >> 1)); + int pixel = (byte >> 4) & 0xF; + int alphaBit = (alphaByte & m) >> shift; + *address = (alphaBit-1)&(colors[pixel]); + x++; + //if w is odd, x may be the same as w, which means we are writing to an unused portion of the bitmap + //but that's okay, since i've added an extra rowByte for just this purpose + address = address + 1; + pixel = byte & 0xF; + m = m >> 1; + alphaBit = (alphaByte & m) >> (shift-1); + //speed up trick here + *address = (alphaBit-1)&(colors[pixel]); +} + +static void editPixelBit8(const int pixelNo, const unsigned char* buf, + const int xorOffset, int& x, int y, const int w, + SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors) +{ + SkPMColor* address = bm->getAddr32(x, y); + int pixel = readByte(buf, xorOffset + pixelNo); + int alphaBit = (alphaByte & m) >> shift; + *address = (alphaBit-1)&(colors[pixel]); +} + +static void editPixelBit24(const int pixelNo, const unsigned char* buf, + const int xorOffset, int& x, int y, const int w, + SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors) +{ + SkPMColor* address = bm->getAddr32(x, y); + int blue = readByte(buf, xorOffset + 3*pixelNo); + int green = readByte(buf, xorOffset + 3*pixelNo + 1); + int red = readByte(buf, xorOffset + 3*pixelNo + 2); + int alphaBit = (alphaByte & m) >> shift; + //alphaBit == 1 => alpha = 0 + int alpha = (alphaBit-1) & 0xFF; + *address = SkPackARGB32(alpha, red & alpha, green & alpha, blue & alpha); +} + +static void editPixelBit32(const int pixelNo, const unsigned char* buf, + const int xorOffset, int& x, int y, const int w, + SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors) +{ + SkPMColor* address = bm->getAddr32(x, y); + int blue = readByte(buf, xorOffset + 4*pixelNo); + int green = readByte(buf, xorOffset + 4*pixelNo + 1); + int red = readByte(buf, xorOffset + 4*pixelNo + 2); + int alphaBit = (alphaByte & m) >> shift; + int alpha = readByte(buf, xorOffset + 4*pixelNo + 3) & ((alphaBit-1)&0xFF); + *address = SkPackARGB32(alpha, red & alpha, green & alpha, blue & alpha); +} + diff --git a/src/images/SkImageDecoder_libjpeg.cpp b/src/images/SkImageDecoder_libjpeg.cpp new file mode 100644 index 0000000000..492de2374f --- /dev/null +++ b/src/images/SkImageDecoder_libjpeg.cpp @@ -0,0 +1,811 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkImageDecoder.h" +#include "SkColorPriv.h" +#include "SkDither.h" +#include "SkScaledBitmapSampler.h" +#include "SkStream.h" +#include "SkTemplates.h" +#include "SkUtils.h" + +#include <stdio.h> +extern "C" { + #include "jpeglib.h" + #include "jerror.h" +} + +// this enables timing code to report milliseconds for an encode +//#define TIME_ENCODE +//#define TIME_DECODE + +// this enables our rgb->yuv code, which is faster than libjpeg on ARM +// disable for the moment, as we have some glitches when width != multiple of 4 +#define WE_CONVERT_TO_YUV + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +class SkJPEGImageDecoder : public SkImageDecoder { +public: + virtual Format getFormat() const { + return kJPEG_Format; + } + +protected: + virtual bool onDecode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config pref, Mode); +}; + +SkImageDecoder* SkImageDecoder_JPEG_Factory(SkStream* stream) { + static const char gHeader[] = { 0xFF, 0xD8, 0xFF }; + static const size_t HEADER_SIZE = sizeof(gHeader); + + char buffer[HEADER_SIZE]; + size_t len = stream->read(buffer, HEADER_SIZE); + + if (len != HEADER_SIZE) { + return NULL; // can't read enough + } + + if (memcmp(buffer, gHeader, HEADER_SIZE)) { + return NULL; + } + + return SkNEW(SkJPEGImageDecoder); +} + +////////////////////////////////////////////////////////////////////////// + +#include "SkTime.h" + +class AutoTimeMillis { +public: + AutoTimeMillis(const char label[]) : fLabel(label) { + if (!fLabel) { + fLabel = ""; + } + fNow = SkTime::GetMSecs(); + } + ~AutoTimeMillis() { + SkDebugf("---- Time (ms): %s %d\n", fLabel, SkTime::GetMSecs() - fNow); + } +private: + const char* fLabel; + SkMSec fNow; +}; + +/* our source struct for directing jpeg to our stream object +*/ +struct sk_source_mgr : jpeg_source_mgr { + sk_source_mgr(SkStream* stream, SkImageDecoder* decoder); + + SkStream* fStream; + const void* fMemoryBase; + size_t fMemoryBaseSize; + SkImageDecoder* fDecoder; + enum { + kBufferSize = 1024 + }; + char fBuffer[kBufferSize]; +}; + +/* Automatically clean up after throwing an exception */ +class JPEGAutoClean { +public: + JPEGAutoClean(): cinfo_ptr(NULL) {} + ~JPEGAutoClean() { + if (cinfo_ptr) { + jpeg_destroy_decompress(cinfo_ptr); + } + } + void set(jpeg_decompress_struct* info) { + cinfo_ptr = info; + } +private: + jpeg_decompress_struct* cinfo_ptr; +}; + +static void sk_init_source(j_decompress_ptr cinfo) { + sk_source_mgr* src = (sk_source_mgr*)cinfo->src; + src->next_input_byte = (const JOCTET*)src->fBuffer; + src->bytes_in_buffer = 0; +} + +static boolean sk_fill_input_buffer(j_decompress_ptr cinfo) { + sk_source_mgr* src = (sk_source_mgr*)cinfo->src; + if (src->fDecoder != NULL && src->fDecoder->shouldCancelDecode()) { + return FALSE; + } + size_t bytes = src->fStream->read(src->fBuffer, sk_source_mgr::kBufferSize); + // note that JPEG is happy with less than the full read, + // as long as the result is non-zero + if (bytes == 0) { + return FALSE; + } + + src->next_input_byte = (const JOCTET*)src->fBuffer; + src->bytes_in_buffer = bytes; + return TRUE; +} + +static void sk_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { + SkASSERT(num_bytes > 0); + + sk_source_mgr* src = (sk_source_mgr*)cinfo->src; + + long bytesToSkip = num_bytes - src->bytes_in_buffer; + + // check if the skip amount exceeds the current buffer + if (bytesToSkip > 0) { + size_t bytes = src->fStream->skip(bytesToSkip); + if (bytes != (size_t)bytesToSkip) { +// SkDebugf("xxxxxxxxxxxxxx failure to skip request %d actual %d\n", bytesToSkip, bytes); + cinfo->err->error_exit((j_common_ptr)cinfo); + } + src->next_input_byte = (const JOCTET*)src->fBuffer; + src->bytes_in_buffer = 0; + } else { + src->next_input_byte += num_bytes; + src->bytes_in_buffer -= num_bytes; + } +} + +static boolean sk_resync_to_restart(j_decompress_ptr cinfo, int desired) { + sk_source_mgr* src = (sk_source_mgr*)cinfo->src; + + // what is the desired param for??? + + if (!src->fStream->rewind()) { + SkDebugf("xxxxxxxxxxxxxx failure to rewind\n"); + cinfo->err->error_exit((j_common_ptr)cinfo); + return FALSE; + } + src->next_input_byte = (const JOCTET*)src->fBuffer; + src->bytes_in_buffer = 0; + return TRUE; +} + +static void sk_term_source(j_decompress_ptr /*cinfo*/) {} + +/////////////////////////////////////////////////////////////////////////////// + +static void skmem_init_source(j_decompress_ptr cinfo) { + sk_source_mgr* src = (sk_source_mgr*)cinfo->src; + src->next_input_byte = (const JOCTET*)src->fMemoryBase; + src->bytes_in_buffer = src->fMemoryBaseSize; +} + +static boolean skmem_fill_input_buffer(j_decompress_ptr cinfo) { + SkDebugf("xxxxxxxxxxxxxx skmem_fill_input_buffer called\n"); + return FALSE; +} + +static void skmem_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { + sk_source_mgr* src = (sk_source_mgr*)cinfo->src; +// SkDebugf("xxxxxxxxxxxxxx skmem_skip_input_data called %d\n", num_bytes); + src->next_input_byte = (const JOCTET*)((const char*)src->next_input_byte + num_bytes); + src->bytes_in_buffer -= num_bytes; +} + +static boolean skmem_resync_to_restart(j_decompress_ptr cinfo, int desired) { + SkDebugf("xxxxxxxxxxxxxx skmem_resync_to_restart called\n"); + return TRUE; +} + +static void skmem_term_source(j_decompress_ptr /*cinfo*/) {} + +/////////////////////////////////////////////////////////////////////////////// + +sk_source_mgr::sk_source_mgr(SkStream* stream, SkImageDecoder* decoder) : fStream(stream) { + fDecoder = decoder; + const void* baseAddr = stream->getMemoryBase(); + if (baseAddr && false) { + fMemoryBase = baseAddr; + fMemoryBaseSize = stream->getLength(); + + init_source = skmem_init_source; + fill_input_buffer = skmem_fill_input_buffer; + skip_input_data = skmem_skip_input_data; + resync_to_restart = skmem_resync_to_restart; + term_source = skmem_term_source; + } else { + fMemoryBase = NULL; + fMemoryBaseSize = 0; + + init_source = sk_init_source; + fill_input_buffer = sk_fill_input_buffer; + skip_input_data = sk_skip_input_data; + resync_to_restart = sk_resync_to_restart; + term_source = sk_term_source; + } +// SkDebugf("**************** use memorybase %p %d\n", fMemoryBase, fMemoryBaseSize); +} + +#include <setjmp.h> + +struct sk_error_mgr : jpeg_error_mgr { + jmp_buf fJmpBuf; +}; + +static void sk_error_exit(j_common_ptr cinfo) { + sk_error_mgr* error = (sk_error_mgr*)cinfo->err; + + (*error->output_message) (cinfo); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy(cinfo); + + longjmp(error->fJmpBuf, -1); +} + +/////////////////////////////////////////////////////////////////////////////// + +static bool skip_src_rows(jpeg_decompress_struct* cinfo, void* buffer, + int count) { + for (int i = 0; i < count; i++) { + JSAMPLE* rowptr = (JSAMPLE*)buffer; + int row_count = jpeg_read_scanlines(cinfo, &rowptr, 1); + if (row_count != 1) { + return false; + } + } + return true; +} + +// This guy exists just to aid in debugging, as it allows debuggers to just +// set a break-point in one place to see all error exists. +static bool return_false(const jpeg_decompress_struct& cinfo, + const SkBitmap& bm, const char msg[]) { +#if 0 + SkDebugf("libjpeg error %d <%s> from %s [%d %d]", cinfo.err->msg_code, + cinfo.err->jpeg_message_table[cinfo.err->msg_code], msg, + bm.width(), bm.height()); +#endif + return false; // must always return false +} + +bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config prefConfig, Mode mode) { +#ifdef TIME_DECODE + AutoTimeMillis atm("JPEG Decode"); +#endif + + SkAutoMalloc srcStorage; + JPEGAutoClean autoClean; + + jpeg_decompress_struct cinfo; + sk_error_mgr sk_err; + sk_source_mgr sk_stream(stream, this); + + cinfo.err = jpeg_std_error(&sk_err); + sk_err.error_exit = sk_error_exit; + + // All objects need to be instantiated before this setjmp call so that + // they will be cleaned up properly if an error occurs. + if (setjmp(sk_err.fJmpBuf)) { + return return_false(cinfo, *bm, "setjmp"); + } + + jpeg_create_decompress(&cinfo); + autoClean.set(&cinfo); + + //jpeg_stdio_src(&cinfo, file); + cinfo.src = &sk_stream; + + int status = jpeg_read_header(&cinfo, true); + if (status != JPEG_HEADER_OK) { + return return_false(cinfo, *bm, "read_header"); + } + + /* Try to fulfill the requested sampleSize. Since jpeg can do it (when it + can) much faster that we, just use their num/denom api to approximate + the size. + */ + int sampleSize = this->getSampleSize(); + + cinfo.dct_method = JDCT_IFAST; + cinfo.scale_num = 1; + cinfo.scale_denom = sampleSize; + + /* this gives about 30% performance improvement. In theory it may + reduce the visual quality, in practice I'm not seeing a difference + */ + cinfo.do_fancy_upsampling = 0; + + /* this gives another few percents */ + cinfo.do_block_smoothing = 0; + + /* default format is RGB */ + cinfo.out_color_space = JCS_RGB; + + SkBitmap::Config config = prefConfig; + // if no user preference, see what the device recommends + if (config == SkBitmap::kNo_Config) + config = SkImageDecoder::GetDeviceConfig(); + + // only these make sense for jpegs + if (config != SkBitmap::kARGB_8888_Config && + config != SkBitmap::kARGB_4444_Config && + config != SkBitmap::kRGB_565_Config) { + config = SkBitmap::kARGB_8888_Config; + } + +#ifdef ANDROID_RGB + cinfo.dither_mode = JDITHER_NONE; + if (config == SkBitmap::kARGB_8888_Config) { + cinfo.out_color_space = JCS_RGBA_8888; + } else if (config == SkBitmap::kRGB_565_Config) { + if (sampleSize == 1) { + // SkScaledBitmapSampler can't handle RGB_565 yet, + // so don't even try. + cinfo.out_color_space = JCS_RGB_565; + if (this->getDitherImage()) { + cinfo.dither_mode = JDITHER_ORDERED; + } + } + } +#endif + + /* image_width and image_height are the original dimensions, available + after jpeg_read_header(). To see the scaled dimensions, we have to call + jpeg_start_decompress(), and then read output_width and output_height. + */ + if (!jpeg_start_decompress(&cinfo)) { + return return_false(cinfo, *bm, "start_decompress"); + } + + /* If we need to better match the request, we might examine the image and + output dimensions, and determine if the downsampling jpeg provided is + not sufficient. If so, we can recompute a modified sampleSize value to + make up the difference. + + To skip this additional scaling, just set sampleSize = 1; below. + */ + sampleSize = sampleSize * cinfo.output_width / cinfo.image_width; + + + // should we allow the Chooser (if present) to pick a config for us??? + if (!this->chooseFromOneChoice(config, cinfo.output_width, + cinfo.output_height)) { + return return_false(cinfo, *bm, "chooseFromOneChoice"); + } + +#ifdef ANDROID_RGB + /* short-circuit the SkScaledBitmapSampler when possible, as this gives + a significant performance boost. + */ + if (sampleSize == 1 && + ((config == SkBitmap::kARGB_8888_Config && + cinfo.out_color_space == JCS_RGBA_8888) || + (config == SkBitmap::kRGB_565_Config && + cinfo.out_color_space == JCS_RGB_565))) + { + bm->setConfig(config, cinfo.output_width, cinfo.output_height); + bm->setIsOpaque(true); + if (SkImageDecoder::kDecodeBounds_Mode == mode) { + return true; + } + if (!this->allocPixelRef(bm, NULL)) { + return return_false(cinfo, *bm, "allocPixelRef"); + } + SkAutoLockPixels alp(*bm); + JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels(); + INT32 const bpr = bm->rowBytes(); + + while (cinfo.output_scanline < cinfo.output_height) { + int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1); + // if row_count == 0, then we didn't get a scanline, so abort. + // if we supported partial images, we might return true in this case + if (0 == row_count) { + return return_false(cinfo, *bm, "read_scanlines"); + } + if (this->shouldCancelDecode()) { + return return_false(cinfo, *bm, "shouldCancelDecode"); + } + rowptr += bpr; + } + jpeg_finish_decompress(&cinfo); + return true; + } +#endif + + // check for supported formats + SkScaledBitmapSampler::SrcConfig sc; + if (3 == cinfo.out_color_components && JCS_RGB == cinfo.out_color_space) { + sc = SkScaledBitmapSampler::kRGB; +#ifdef ANDROID_RGB + } else if (JCS_RGBA_8888 == cinfo.out_color_space) { + sc = SkScaledBitmapSampler::kRGBX; + //} else if (JCS_RGB_565 == cinfo.out_color_space) { + // sc = SkScaledBitmapSampler::kRGB_565; +#endif + } else if (1 == cinfo.out_color_components && + JCS_GRAYSCALE == cinfo.out_color_space) { + sc = SkScaledBitmapSampler::kGray; + } else { + return return_false(cinfo, *bm, "jpeg colorspace"); + } + + SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height, + sampleSize); + + bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight()); + // jpegs are always opauqe (i.e. have no per-pixel alpha) + bm->setIsOpaque(true); + + if (SkImageDecoder::kDecodeBounds_Mode == mode) { + return true; + } + if (!this->allocPixelRef(bm, NULL)) { + return return_false(cinfo, *bm, "allocPixelRef"); + } + + SkAutoLockPixels alp(*bm); + if (!sampler.begin(bm, sc, this->getDitherImage())) { + return return_false(cinfo, *bm, "sampler.begin"); + } + + uint8_t* srcRow = (uint8_t*)srcStorage.alloc(cinfo.output_width * 4); + + // Possibly skip initial rows [sampler.srcY0] + if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) { + return return_false(cinfo, *bm, "skip rows"); + } + + // now loop through scanlines until y == bm->height() - 1 + for (int y = 0;; y++) { + JSAMPLE* rowptr = (JSAMPLE*)srcRow; + int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1); + if (0 == row_count) { + return return_false(cinfo, *bm, "read_scanlines"); + } + if (this->shouldCancelDecode()) { + return return_false(cinfo, *bm, "shouldCancelDecode"); + } + + sampler.next(srcRow); + if (bm->height() - 1 == y) { + // we're done + break; + } + + if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) { + return return_false(cinfo, *bm, "skip rows"); + } + } + + // we formally skip the rest, so we don't get a complaint from libjpeg + if (!skip_src_rows(&cinfo, srcRow, + cinfo.output_height - cinfo.output_scanline)) { + return return_false(cinfo, *bm, "skip rows"); + } + jpeg_finish_decompress(&cinfo); + +// SkDebugf("------------------- bm2 size %d [%d %d] %d\n", bm->getSize(), bm->width(), bm->height(), bm->config()); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_SUPPORT_IMAGE_ENCODE + +#include "SkColorPriv.h" + +// taken from jcolor.c in libjpeg +#if 0 // 16bit - precise but slow + #define CYR 19595 // 0.299 + #define CYG 38470 // 0.587 + #define CYB 7471 // 0.114 + + #define CUR -11059 // -0.16874 + #define CUG -21709 // -0.33126 + #define CUB 32768 // 0.5 + + #define CVR 32768 // 0.5 + #define CVG -27439 // -0.41869 + #define CVB -5329 // -0.08131 + + #define CSHIFT 16 +#else // 8bit - fast, slightly less precise + #define CYR 77 // 0.299 + #define CYG 150 // 0.587 + #define CYB 29 // 0.114 + + #define CUR -43 // -0.16874 + #define CUG -85 // -0.33126 + #define CUB 128 // 0.5 + + #define CVR 128 // 0.5 + #define CVG -107 // -0.41869 + #define CVB -21 // -0.08131 + + #define CSHIFT 8 +#endif + +static void rgb2yuv_32(uint8_t dst[], SkPMColor c) { + int r = SkGetPackedR32(c); + int g = SkGetPackedG32(c); + int b = SkGetPackedB32(c); + + int y = ( CYR*r + CYG*g + CYB*b ) >> CSHIFT; + int u = ( CUR*r + CUG*g + CUB*b ) >> CSHIFT; + int v = ( CVR*r + CVG*g + CVB*b ) >> CSHIFT; + + dst[0] = SkToU8(y); + dst[1] = SkToU8(u + 128); + dst[2] = SkToU8(v + 128); +} + +static void rgb2yuv_4444(uint8_t dst[], U16CPU c) { + int r = SkGetPackedR4444(c); + int g = SkGetPackedG4444(c); + int b = SkGetPackedB4444(c); + + int y = ( CYR*r + CYG*g + CYB*b ) >> (CSHIFT - 4); + int u = ( CUR*r + CUG*g + CUB*b ) >> (CSHIFT - 4); + int v = ( CVR*r + CVG*g + CVB*b ) >> (CSHIFT - 4); + + dst[0] = SkToU8(y); + dst[1] = SkToU8(u + 128); + dst[2] = SkToU8(v + 128); +} + +static void rgb2yuv_16(uint8_t dst[], U16CPU c) { + int r = SkGetPackedR16(c); + int g = SkGetPackedG16(c); + int b = SkGetPackedB16(c); + + int y = ( 2*CYR*r + CYG*g + 2*CYB*b ) >> (CSHIFT - 2); + int u = ( 2*CUR*r + CUG*g + 2*CUB*b ) >> (CSHIFT - 2); + int v = ( 2*CVR*r + CVG*g + 2*CVB*b ) >> (CSHIFT - 2); + + dst[0] = SkToU8(y); + dst[1] = SkToU8(u + 128); + dst[2] = SkToU8(v + 128); +} + +/////////////////////////////////////////////////////////////////////////////// + +typedef void (*WriteScanline)(uint8_t* SK_RESTRICT dst, + const void* SK_RESTRICT src, int width, + const SkPMColor* SK_RESTRICT ctable); + +static void Write_32_YUV(uint8_t* SK_RESTRICT dst, + const void* SK_RESTRICT srcRow, int width, + const SkPMColor*) { + const uint32_t* SK_RESTRICT src = (const uint32_t*)srcRow; + while (--width >= 0) { +#ifdef WE_CONVERT_TO_YUV + rgb2yuv_32(dst, *src++); +#else + uint32_t c = *src++; + dst[0] = SkGetPackedR32(c); + dst[1] = SkGetPackedG32(c); + dst[2] = SkGetPackedB32(c); +#endif + dst += 3; + } +} + +static void Write_4444_YUV(uint8_t* SK_RESTRICT dst, + const void* SK_RESTRICT srcRow, int width, + const SkPMColor*) { + const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)srcRow; + while (--width >= 0) { +#ifdef WE_CONVERT_TO_YUV + rgb2yuv_4444(dst, *src++); +#else + SkPMColor16 c = *src++; + dst[0] = SkPacked4444ToR32(c); + dst[1] = SkPacked4444ToG32(c); + dst[2] = SkPacked4444ToB32(c); +#endif + dst += 3; + } +} + +static void Write_16_YUV(uint8_t* SK_RESTRICT dst, + const void* SK_RESTRICT srcRow, int width, + const SkPMColor*) { + const uint16_t* SK_RESTRICT src = (const uint16_t*)srcRow; + while (--width >= 0) { +#ifdef WE_CONVERT_TO_YUV + rgb2yuv_16(dst, *src++); +#else + uint16_t c = *src++; + dst[0] = SkPacked16ToR32(c); + dst[1] = SkPacked16ToG32(c); + dst[2] = SkPacked16ToB32(c); +#endif + dst += 3; + } +} + +static void Write_Index_YUV(uint8_t* SK_RESTRICT dst, + const void* SK_RESTRICT srcRow, int width, + const SkPMColor* SK_RESTRICT ctable) { + const uint8_t* SK_RESTRICT src = (const uint8_t*)srcRow; + while (--width >= 0) { +#ifdef WE_CONVERT_TO_YUV + rgb2yuv_32(dst, ctable[*src++]); +#else + uint32_t c = ctable[*src++]; + dst[0] = SkGetPackedR32(c); + dst[1] = SkGetPackedG32(c); + dst[2] = SkGetPackedB32(c); +#endif + dst += 3; + } +} + +static WriteScanline ChooseWriter(const SkBitmap& bm) { + switch (bm.config()) { + case SkBitmap::kARGB_8888_Config: + return Write_32_YUV; + case SkBitmap::kRGB_565_Config: + return Write_16_YUV; + case SkBitmap::kARGB_4444_Config: + return Write_4444_YUV; + case SkBitmap::kIndex8_Config: + return Write_Index_YUV; + default: + return NULL; + } +} + +struct sk_destination_mgr : jpeg_destination_mgr { + sk_destination_mgr(SkWStream* stream); + + SkWStream* fStream; + + enum { + kBufferSize = 1024 + }; + uint8_t fBuffer[kBufferSize]; +}; + +static void sk_init_destination(j_compress_ptr cinfo) { + sk_destination_mgr* dest = (sk_destination_mgr*)cinfo->dest; + + dest->next_output_byte = dest->fBuffer; + dest->free_in_buffer = sk_destination_mgr::kBufferSize; +} + +static boolean sk_empty_output_buffer(j_compress_ptr cinfo) { + sk_destination_mgr* dest = (sk_destination_mgr*)cinfo->dest; + +// if (!dest->fStream->write(dest->fBuffer, sk_destination_mgr::kBufferSize - dest->free_in_buffer)) + if (!dest->fStream->write(dest->fBuffer, sk_destination_mgr::kBufferSize)) { + ERREXIT(cinfo, JERR_FILE_WRITE); + return false; + } + + dest->next_output_byte = dest->fBuffer; + dest->free_in_buffer = sk_destination_mgr::kBufferSize; + return TRUE; +} + +static void sk_term_destination (j_compress_ptr cinfo) { + sk_destination_mgr* dest = (sk_destination_mgr*)cinfo->dest; + + size_t size = sk_destination_mgr::kBufferSize - dest->free_in_buffer; + if (size > 0) { + if (!dest->fStream->write(dest->fBuffer, size)) { + ERREXIT(cinfo, JERR_FILE_WRITE); + return; + } + } + dest->fStream->flush(); +} + +sk_destination_mgr::sk_destination_mgr(SkWStream* stream) + : fStream(stream) { + this->init_destination = sk_init_destination; + this->empty_output_buffer = sk_empty_output_buffer; + this->term_destination = sk_term_destination; +} + +class SkJPEGImageEncoder : public SkImageEncoder { +protected: + virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) { +#ifdef TIME_ENCODE + AutoTimeMillis atm("JPEG Encode"); +#endif + + const WriteScanline writer = ChooseWriter(bm); + if (NULL == writer) { + return false; + } + + SkAutoLockPixels alp(bm); + if (NULL == bm.getPixels()) { + return false; + } + + jpeg_compress_struct cinfo; + sk_error_mgr sk_err; + sk_destination_mgr sk_wstream(stream); + + // allocate these before set call setjmp + SkAutoMalloc oneRow; + SkAutoLockColors ctLocker; + + cinfo.err = jpeg_std_error(&sk_err); + sk_err.error_exit = sk_error_exit; + if (setjmp(sk_err.fJmpBuf)) { + return false; + } + jpeg_create_compress(&cinfo); + + cinfo.dest = &sk_wstream; + cinfo.image_width = bm.width(); + cinfo.image_height = bm.height(); + cinfo.input_components = 3; +#ifdef WE_CONVERT_TO_YUV + cinfo.in_color_space = JCS_YCbCr; +#else + cinfo.in_color_space = JCS_RGB; +#endif + cinfo.input_gamma = 1; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); + cinfo.dct_method = JDCT_IFAST; + + jpeg_start_compress(&cinfo, TRUE); + + const int width = bm.width(); + uint8_t* oneRowP = (uint8_t*)oneRow.alloc(width * 3); + + const SkPMColor* colors = ctLocker.lockColors(bm); + const void* srcRow = bm.getPixels(); + + while (cinfo.next_scanline < cinfo.image_height) { + JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ + + writer(oneRowP, srcRow, width, colors); + row_pointer[0] = oneRowP; + (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); + srcRow = (const void*)((const char*)srcRow + bm.rowBytes()); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + + return true; + } +}; + +SkImageEncoder* SkImageEncoder_JPEG_Factory(); +SkImageEncoder* SkImageEncoder_JPEG_Factory() { + return SkNEW(SkJPEGImageEncoder); +} + +#endif /* SK_SUPPORT_IMAGE_ENCODE */ + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG + +void SkImageDecoder::UnitTest() { + SkBitmap bm; + + (void)SkImageDecoder::DecodeFile("logo.jpg", &bm); +} + +#endif diff --git a/src/images/SkImageDecoder_libpng.cpp b/src/images/SkImageDecoder_libpng.cpp new file mode 100644 index 0000000000..862ebf1d98 --- /dev/null +++ b/src/images/SkImageDecoder_libpng.cpp @@ -0,0 +1,795 @@ +/* libs/graphics/images/SkImageDecoder_libpng.cpp +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "SkImageDecoder.h" +#include "SkColor.h" +#include "SkColorPriv.h" +#include "SkDither.h" +#include "SkMath.h" +#include "SkScaledBitmapSampler.h" +#include "SkStream.h" +#include "SkTemplates.h" +#include "SkUtils.h" + +extern "C" { +#include "png.h" +} + +class SkPNGImageDecoder : public SkImageDecoder { +public: + virtual Format getFormat() const { + return kPNG_Format; + } + +protected: + virtual bool onDecode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config pref, Mode); +}; + +#ifndef png_jmpbuf +# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf) +#endif + +#define PNG_BYTES_TO_CHECK 4 + +/* Automatically clean up after throwing an exception */ +struct PNGAutoClean { + PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {} + ~PNGAutoClean() { + png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL); + } +private: + png_structp png_ptr; + png_infop info_ptr; +}; + +SkImageDecoder* SkImageDecoder_PNG_Factory(SkStream* stream) { + char buf[PNG_BYTES_TO_CHECK]; + if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK && + !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) { + return SkNEW(SkPNGImageDecoder); + } + return NULL; +} + +static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) { + SkStream* sk_stream = (SkStream*) png_ptr->io_ptr; + size_t bytes = sk_stream->read(data, length); + if (bytes != length) { + png_error(png_ptr, "Read Error!"); + } +} + +static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) { + SkImageDecoder::Peeker* peeker = + (SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr); + // peek() returning true means continue decoding + return peeker->peek((const char*)chunk->name, chunk->data, chunk->size) ? + 1 : -1; +} + +static void sk_error_fn(png_structp png_ptr, png_const_charp msg) { +#if 0 + SkDebugf("------ png error %s\n", msg); +#endif + longjmp(png_jmpbuf(png_ptr), 1); +} + +static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) { + for (int i = 0; i < count; i++) { + uint8_t* tmp = storage; + png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1); + } +} + +static bool pos_le(int value, int max) { + return value > 0 && value <= max; +} + +static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) { + SkASSERT(bm->config() == SkBitmap::kARGB_8888_Config); + + bool reallyHasAlpha = false; + + for (int y = bm->height() - 1; y >= 0; --y) { + SkPMColor* p = bm->getAddr32(0, y); + for (int x = bm->width() - 1; x >= 0; --x) { + if (match == *p) { + *p = 0; + reallyHasAlpha = true; + } + p += 1; + } + } + return reallyHasAlpha; +} + +bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, + SkBitmap::Config prefConfig, Mode mode) { +// SkAutoTrace apr("SkPNGImageDecoder::onDecode"); + + /* Create and initialize the png_struct with the desired error handler + * functions. If you want to use the default stderr and longjump method, + * you can supply NULL for the last three parameters. We also supply the + * the compiler header file version, so that we know if the application + * was compiled with a compatible version of the library. */ + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, + NULL, sk_error_fn, NULL); + // png_voidp user_error_ptr, user_error_fn, user_warning_fn); + if (png_ptr == NULL) { + return false; + } + + /* Allocate/initialize the memory for image information. */ + png_infop info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL); + return false; + } + + PNGAutoClean autoClean(png_ptr, info_ptr); + + /* Set error handling if you are using the setjmp/longjmp method (this is + * the normal method of doing things with libpng). REQUIRED unless you + * set up your own error handlers in the png_create_read_struct() earlier. + */ + if (setjmp(png_jmpbuf(png_ptr))) { + return false; + } + + /* If you are using replacement read functions, instead of calling + * png_init_io() here you would call: + */ + png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn); + /* where user_io_ptr is a structure you want available to the callbacks */ + /* If we have already read some of the signature */ +// png_set_sig_bytes(png_ptr, 0 /* sig_read */ ); + + // hookup our peeker so we can see any user-chunks the caller may be interested in + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0); + if (this->getPeeker()) { + png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk); + } + + /* The call to png_read_info() gives us all of the information from the + * PNG file before the first IDAT (image data chunk). */ + png_read_info(png_ptr, info_ptr); + png_uint_32 origWidth, origHeight; + int bit_depth, color_type, interlace_type; + png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bit_depth, &color_type, + &interlace_type, int_p_NULL, int_p_NULL); + + /* tell libpng to strip 16 bit/color files down to 8 bits/color */ + if (bit_depth == 16) { + png_set_strip_16(png_ptr); + } + /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single + * byte into separate bytes (useful for paletted and grayscale images). */ + if (bit_depth < 8) { + png_set_packing(png_ptr); + } + /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */ + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { + png_set_gray_1_2_4_to_8(png_ptr); + } + + /* Make a grayscale image into RGB. */ + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + } + + SkBitmap::Config config; + bool hasAlpha = false; + bool doDither = this->getDitherImage(); + SkPMColor theTranspColor = 0; // 0 tells us not to try to match + + // check for sBIT chunk data, in case we should disable dithering because + // our data is not truely 8bits per component + if (doDither) { +#if 0 + SkDebugf("----- sBIT %d %d %d %d\n", info_ptr->sig_bit.red, + info_ptr->sig_bit.green, info_ptr->sig_bit.blue, + info_ptr->sig_bit.alpha); +#endif + // 0 seems to indicate no information available + if (pos_le(info_ptr->sig_bit.red, SK_R16_BITS) && + pos_le(info_ptr->sig_bit.green, SK_G16_BITS) && + pos_le(info_ptr->sig_bit.blue, SK_B16_BITS)) { + doDither = false; + } + } + + if (color_type == PNG_COLOR_TYPE_PALETTE) { + config = SkBitmap::kIndex8_Config; // defer sniffing for hasAlpha + } else { + png_color_16p transpColor = NULL; + int numTransp = 0; + + png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor); + + bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS); + + if (valid && numTransp == 1 && transpColor != NULL) { + /* Compute our transparent color, which we'll match against later. + We don't really handle 16bit components properly here, since we + do our compare *after* the values have been knocked down to 8bit + which means we will find more matches than we should. The real + fix seems to be to see the actual 16bit components, do the + compare, and then knock it down to 8bits ourselves. + */ + if (color_type & PNG_COLOR_MASK_COLOR) { + if (16 == bit_depth) { + theTranspColor = SkPackARGB32(0xFF, transpColor->red >> 8, + transpColor->green >> 8, transpColor->blue >> 8); + } else { + theTranspColor = SkPackARGB32(0xFF, transpColor->red, + transpColor->green, transpColor->blue); + } + } else { // gray + if (16 == bit_depth) { + theTranspColor = SkPackARGB32(0xFF, transpColor->gray >> 8, + transpColor->gray >> 8, transpColor->gray >> 8); + } else { + theTranspColor = SkPackARGB32(0xFF, transpColor->gray, + transpColor->gray, transpColor->gray); + } + } + } + + if (valid || + PNG_COLOR_TYPE_RGB_ALPHA == color_type || + PNG_COLOR_TYPE_GRAY_ALPHA == color_type) { + hasAlpha = true; + config = SkBitmap::kARGB_8888_Config; + } else { // we get to choose the config + config = prefConfig; + if (config == SkBitmap::kNo_Config) { + config = SkImageDecoder::GetDeviceConfig(); + } + if (config != SkBitmap::kRGB_565_Config && + config != SkBitmap::kARGB_4444_Config) { + config = SkBitmap::kARGB_8888_Config; + } + } + } + + if (!this->chooseFromOneChoice(config, origWidth, origHeight)) { + return false; + } + + const int sampleSize = this->getSampleSize(); + SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize); + + decodedBitmap->setConfig(config, sampler.scaledWidth(), + sampler.scaledHeight(), 0); + if (SkImageDecoder::kDecodeBounds_Mode == mode) { + return true; + } + + // from here down we are concerned with colortables and pixels + + // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype + // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we + // draw lots faster if we can flag the bitmap has being opaque + bool reallyHasAlpha = false; + + SkColorTable* colorTable = NULL; + + if (color_type == PNG_COLOR_TYPE_PALETTE) { + int num_palette; + png_colorp palette; + png_bytep trans; + int num_trans; + + png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); + + /* BUGGY IMAGE WORKAROUND + + We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count + which is a problem since we use the byte as an index. To work around this we grow + the colortable by 1 (if its < 256) and duplicate the last color into that slot. + */ + int colorCount = num_palette + (num_palette < 256); + + colorTable = SkNEW_ARGS(SkColorTable, (colorCount)); + + SkPMColor* colorPtr = colorTable->lockColors(); + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL); + hasAlpha = (num_trans > 0); + } else { + num_trans = 0; + colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag); + } + // check for bad images that might make us crash + if (num_trans > num_palette) { + num_trans = num_palette; + } + + int index = 0; + int transLessThanFF = 0; + + for (; index < num_trans; index++) { + transLessThanFF |= (int)*trans - 0xFF; + *colorPtr++ = SkPreMultiplyARGB(*trans++, palette->red, palette->green, palette->blue); + palette++; + } + reallyHasAlpha |= (transLessThanFF < 0); + + for (; index < num_palette; index++) { + *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue); + palette++; + } + + // see BUGGY IMAGE WORKAROUND comment above + if (num_palette < 256) { + *colorPtr = colorPtr[-1]; + } + colorTable->unlockColors(true); + } + + SkAutoUnref aur(colorTable); + + if (!this->allocPixelRef(decodedBitmap, colorTable)) { + return false; + } + + SkAutoLockPixels alp(*decodedBitmap); + + /* swap the RGBA or GA data to ARGB or AG (or BGRA to ABGR) */ +// if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) +// ; // png_set_swap_alpha(png_ptr); + + /* swap bytes of 16 bit files to least significant byte first */ + // png_set_swap(png_ptr); + + /* Add filler (or alpha) byte (before/after each RGB triplet) */ + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY) { + png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); + } + + /* Turn on interlace handling. REQUIRED if you are not using + * png_read_image(). To see how to handle interlacing passes, + * see the png_read_row() method below: + */ + const int number_passes = interlace_type != PNG_INTERLACE_NONE ? + png_set_interlace_handling(png_ptr) : 1; + + /* Optional call to gamma correct and add the background to the palette + * and update info structure. REQUIRED if you are expecting libpng to + * update the palette for you (ie you selected such a transform above). + */ + png_read_update_info(png_ptr, info_ptr); + + if (SkBitmap::kIndex8_Config == config && 1 == sampleSize) { + for (int i = 0; i < number_passes; i++) { + for (png_uint_32 y = 0; y < origHeight; y++) { + uint8_t* bmRow = decodedBitmap->getAddr8(0, y); + png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1); + } + } + } else { + SkScaledBitmapSampler::SrcConfig sc; + int srcBytesPerPixel = 4; + + if (SkBitmap::kIndex8_Config == config) { + sc = SkScaledBitmapSampler::kIndex; + srcBytesPerPixel = 1; + } else if (hasAlpha) { + sc = SkScaledBitmapSampler::kRGBA; + } else { + sc = SkScaledBitmapSampler::kRGBX; + } + + SkAutoMalloc storage(origWidth * srcBytesPerPixel); + const int height = decodedBitmap->height(); + + for (int i = 0; i < number_passes; i++) { + if (!sampler.begin(decodedBitmap, sc, doDither)) { + return false; + } + + uint8_t* srcRow = (uint8_t*)storage.get(); + skip_src_rows(png_ptr, srcRow, sampler.srcY0()); + + for (int y = 0; y < height; y++) { + uint8_t* tmp = srcRow; + png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1); + reallyHasAlpha |= sampler.next(srcRow); + if (y < height - 1) { + skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1); + } + } + + // skip the rest of the rows (if any) + png_uint_32 read = (height - 1) * sampler.srcDY() + + sampler.srcY0() + 1; + SkASSERT(read <= origHeight); + skip_src_rows(png_ptr, srcRow, origHeight - read); + } + + if (hasAlpha && !reallyHasAlpha) { +#if 0 + SkDEBUGF(("Image doesn't really have alpha [%d %d]\n", + origWidth, origHeight)); +#endif + } + } + + /* read rest of file, and get additional chunks in info_ptr - REQUIRED */ + png_read_end(png_ptr, info_ptr); + + if (0 != theTranspColor) { + reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor); + } + decodedBitmap->setIsOpaque(!reallyHasAlpha); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_SUPPORT_IMAGE_ENCODE + +#include "SkColorPriv.h" +#include "SkUnPreMultiply.h" + +static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) { + SkWStream* sk_stream = (SkWStream*)png_ptr->io_ptr; + if (!sk_stream->write(data, len)) { + png_error(png_ptr, "sk_write_fn Error!"); + } +} + +typedef void (*transform_scanline_proc)(const char* SK_RESTRICT src, + int width, char* SK_RESTRICT dst); + +static void transform_scanline_565(const char* SK_RESTRICT src, int width, + char* SK_RESTRICT dst) { + const uint16_t* SK_RESTRICT srcP = (const uint16_t*)src; + for (int i = 0; i < width; i++) { + unsigned c = *srcP++; + *dst++ = SkPacked16ToR32(c); + *dst++ = SkPacked16ToG32(c); + *dst++ = SkPacked16ToB32(c); + } +} + +static void transform_scanline_888(const char* SK_RESTRICT src, int width, + char* SK_RESTRICT dst) { + const SkPMColor* SK_RESTRICT srcP = (const SkPMColor*)src; + for (int i = 0; i < width; i++) { + SkPMColor c = *srcP++; + *dst++ = SkGetPackedR32(c); + *dst++ = SkGetPackedG32(c); + *dst++ = SkGetPackedB32(c); + } +} + +static void transform_scanline_444(const char* SK_RESTRICT src, int width, + char* SK_RESTRICT dst) { + const SkPMColor16* SK_RESTRICT srcP = (const SkPMColor16*)src; + for (int i = 0; i < width; i++) { + SkPMColor16 c = *srcP++; + *dst++ = SkPacked4444ToR32(c); + *dst++ = SkPacked4444ToG32(c); + *dst++ = SkPacked4444ToB32(c); + } +} + +static void transform_scanline_8888(const char* SK_RESTRICT src, int width, + char* SK_RESTRICT dst) { + const SkPMColor* SK_RESTRICT srcP = (const SkPMColor*)src; + const SkUnPreMultiply::Scale* SK_RESTRICT table = + SkUnPreMultiply::GetScaleTable(); + + for (int i = 0; i < width; i++) { + SkPMColor c = *srcP++; + unsigned a = SkGetPackedA32(c); + unsigned r = SkGetPackedR32(c); + unsigned g = SkGetPackedG32(c); + unsigned b = SkGetPackedB32(c); + + if (0 != a && 255 != a) { + SkUnPreMultiply::Scale scale = table[a]; + r = SkUnPreMultiply::ApplyScale(scale, r); + g = SkUnPreMultiply::ApplyScale(scale, g); + b = SkUnPreMultiply::ApplyScale(scale, b); + } + *dst++ = r; + *dst++ = g; + *dst++ = b; + *dst++ = a; + } +} + +static void transform_scanline_4444(const char* SK_RESTRICT src, int width, + char* SK_RESTRICT dst) { + const SkPMColor16* SK_RESTRICT srcP = (const SkPMColor16*)src; + const SkUnPreMultiply::Scale* SK_RESTRICT table = + SkUnPreMultiply::GetScaleTable(); + + for (int i = 0; i < width; i++) { + SkPMColor16 c = *srcP++; + unsigned a = SkPacked4444ToA32(c); + unsigned r = SkPacked4444ToR32(c); + unsigned g = SkPacked4444ToG32(c); + unsigned b = SkPacked4444ToB32(c); + + if (0 != a && 255 != a) { + SkUnPreMultiply::Scale scale = table[a]; + r = SkUnPreMultiply::ApplyScale(scale, r); + g = SkUnPreMultiply::ApplyScale(scale, g); + b = SkUnPreMultiply::ApplyScale(scale, b); + } + *dst++ = r; + *dst++ = g; + *dst++ = b; + *dst++ = a; + } +} + +static void transform_scanline_index8(const char* SK_RESTRICT src, int width, + char* SK_RESTRICT dst) { + memcpy(dst, src, width); +} + +static transform_scanline_proc choose_proc(SkBitmap::Config config, + bool hasAlpha) { + // we don't care about search on alpha if we're kIndex8, since only the + // colortable packing cares about that distinction, not the pixels + if (SkBitmap::kIndex8_Config == config) { + hasAlpha = false; // we store false in the table entries for kIndex8 + } + + static const struct { + SkBitmap::Config fConfig; + bool fHasAlpha; + transform_scanline_proc fProc; + } gMap[] = { + { SkBitmap::kRGB_565_Config, false, transform_scanline_565 }, + { SkBitmap::kARGB_8888_Config, false, transform_scanline_888 }, + { SkBitmap::kARGB_8888_Config, true, transform_scanline_8888 }, + { SkBitmap::kARGB_4444_Config, false, transform_scanline_444 }, + { SkBitmap::kARGB_4444_Config, true, transform_scanline_4444 }, + { SkBitmap::kIndex8_Config, false, transform_scanline_index8 }, + }; + + for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) { + if (gMap[i].fConfig == config && gMap[i].fHasAlpha == hasAlpha) { + return gMap[i].fProc; + } + } + sk_throw(); + return NULL; +} + +// return the minimum legal bitdepth (by png standards) for this many colortable +// entries. SkBitmap always stores in 8bits per pixel, but for colorcount <= 16, +// we can use fewer bits per in png +static int computeBitDepth(int colorCount) { +#if 0 + int bits = SkNextLog2(colorCount); + SkASSERT(bits >= 1 && bits <= 8); + // now we need bits itself to be a power of 2 (e.g. 1, 2, 4, 8) + return SkNextPow2(bits); +#else + // for the moment, we don't know how to pack bitdepth < 8 + return 8; +#endif +} + +/* Pack palette[] with the corresponding colors, and if hasAlpha is true, also + pack trans[] and return the number of trans[] entries written. If hasAlpha + is false, the return value will always be 0. + + Note: this routine takes care of unpremultiplying the RGB values when we + have alpha in the colortable, since png doesn't support premul colors +*/ +static int pack_palette(SkColorTable* ctable, png_color* SK_RESTRICT palette, + png_byte* SK_RESTRICT trans, bool hasAlpha) { + SkAutoLockColors alc(ctable); + const SkPMColor* SK_RESTRICT colors = alc.colors(); + const int ctCount = ctable->count(); + int i, num_trans = 0; + + if (hasAlpha) { + /* first see if we have some number of fully opaque at the end of the + ctable. PNG allows num_trans < num_palette, but all of the trans + entries must come first in the palette. If I was smarter, I'd + reorder the indices and ctable so that all non-opaque colors came + first in the palette. But, since that would slow down the encode, + I'm leaving the indices and ctable order as is, and just looking + at the tail of the ctable for opaqueness. + */ + num_trans = ctCount; + for (i = ctCount - 1; i >= 0; --i) { + if (SkGetPackedA32(colors[i]) != 0xFF) { + break; + } + num_trans -= 1; + } + + const SkUnPreMultiply::Scale* SK_RESTRICT table = + SkUnPreMultiply::GetScaleTable(); + + for (i = 0; i < num_trans; i++) { + const SkPMColor c = *colors++; + const unsigned a = SkGetPackedA32(c); + const SkUnPreMultiply::Scale s = table[a]; + trans[i] = a; + palette[i].red = SkUnPreMultiply::ApplyScale(s, SkGetPackedR32(c)); + palette[i].green = SkUnPreMultiply::ApplyScale(s,SkGetPackedG32(c)); + palette[i].blue = SkUnPreMultiply::ApplyScale(s, SkGetPackedB32(c)); + } + // now fall out of this if-block to use common code for the trailing + // opaque entries + } + + // these (remaining) entries are opaque + for (i = num_trans; i < ctCount; i++) { + SkPMColor c = *colors++; + palette[i].red = SkGetPackedR32(c); + palette[i].green = SkGetPackedG32(c); + palette[i].blue = SkGetPackedB32(c); + } + return num_trans; +} + +class SkPNGImageEncoder : public SkImageEncoder { +protected: + virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality); +}; + +bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, + int /*quality*/) { + SkBitmap::Config config = bitmap.getConfig(); + + const bool hasAlpha = !bitmap.isOpaque(); + int colorType = PNG_COLOR_MASK_COLOR; + int bitDepth = 8; // default for color + png_color_8 sig_bit; + + switch (config) { + case SkBitmap::kIndex8_Config: + colorType |= PNG_COLOR_MASK_PALETTE; + // fall through to the ARGB_8888 case + case SkBitmap::kARGB_8888_Config: + sig_bit.red = 8; + sig_bit.green = 8; + sig_bit.blue = 8; + sig_bit.alpha = 8; + break; + case SkBitmap::kARGB_4444_Config: + sig_bit.red = 4; + sig_bit.green = 4; + sig_bit.blue = 4; + sig_bit.alpha = 4; + break; + case SkBitmap::kRGB_565_Config: + sig_bit.red = 5; + sig_bit.green = 6; + sig_bit.blue = 5; + sig_bit.alpha = 0; + break; + default: + return false; + } + + if (hasAlpha) { + // don't specify alpha if we're a palette, even if our ctable has alpha + if (!(colorType & PNG_COLOR_MASK_PALETTE)) { + colorType |= PNG_COLOR_MASK_ALPHA; + } + } else { + sig_bit.alpha = 0; + } + + SkAutoLockPixels alp(bitmap); + // readyToDraw checks for pixels (and colortable if that is required) + if (!bitmap.readyToDraw()) { + return false; + } + + // we must do this after we have locked the pixels + SkColorTable* ctable = bitmap.getColorTable(); + if (NULL != ctable) { + if (ctable->count() == 0) { + return false; + } + // check if we can store in fewer than 8 bits + bitDepth = computeBitDepth(ctable->count()); + } + + png_structp png_ptr; + png_infop info_ptr; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn, + NULL); + if (NULL == png_ptr) { + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (NULL == info_ptr) { + png_destroy_write_struct(&png_ptr, png_infopp_NULL); + return false; + } + + /* Set error handling. REQUIRED if you aren't supplying your own + * error handling functions in the png_create_write_struct() call. + */ + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return false; + } + + png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL); + + /* Set the image information here. Width and height are up to 2^31, + * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on + * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, + * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, + * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or + * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST + * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED + */ + + png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(), + bitDepth, colorType, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + +#if 0 // need to support this some day + /* set the palette if there is one. REQUIRED for indexed-color images */ + palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH + * png_sizeof (png_color)); + /* ... set palette colors ... */ + png_set_PLTE(png_ptr, info_ptr, palette, PNG_MAX_PALETTE_LENGTH); + /* You must not free palette here, because png_set_PLTE only makes a link to + the palette that you malloced. Wait until you are about to destroy + the png structure. */ +#endif + + png_set_sBIT(png_ptr, info_ptr, &sig_bit); + png_write_info(png_ptr, info_ptr); + + const char* srcImage = (const char*)bitmap.getPixels(); + SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2); + char* storage = (char*)rowStorage.get(); + transform_scanline_proc proc = choose_proc(config, hasAlpha); + + for (int y = 0; y < bitmap.height(); y++) { + png_bytep row_ptr = (png_bytep)storage; + proc(srcImage, bitmap.width(), storage); + png_write_rows(png_ptr, &row_ptr, 1); + srcImage += bitmap.rowBytes(); + } + + png_write_end(png_ptr, info_ptr); + + /* clean up after the write, and free any memory allocated */ + png_destroy_write_struct(&png_ptr, &info_ptr); + return true; +} + +SkImageEncoder* SkImageEncoder_PNG_Factory(); +SkImageEncoder* SkImageEncoder_PNG_Factory() { + return SkNEW(SkPNGImageEncoder); +} + +#endif /* SK_SUPPORT_IMAGE_ENCODE */ diff --git a/src/images/SkImageDecoder_libpvjpeg.cpp b/src/images/SkImageDecoder_libpvjpeg.cpp new file mode 100644 index 0000000000..91777418b2 --- /dev/null +++ b/src/images/SkImageDecoder_libpvjpeg.cpp @@ -0,0 +1,206 @@ +#include "SkImageDecoder.h" +#include "SkColor.h" +#include "SkColorPriv.h" +#include "SkDither.h" +#include "SkMath.h" +#include "SkStream.h" +#include "SkTemplates.h" +#include "SkUtils.h" + +extern void ValidateHeap(); + +class SkPVJPEGImageDecoder : public SkImageDecoder { +protected: + virtual bool onDecode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config pref, Mode); + +private: + enum { + STORAGE_SIZE = 8 * 1024 + }; + char fStorage[STORAGE_SIZE]; +}; + +SkImageDecoder* SkImageDecoder_PVJPEG_Factory(SkStream* stream) +{ + return SkNEW(SkPVJPEGImageDecoder); +} + +#include "pvjpgdecoderinterface.h" +#include "pvjpgdecoder_factory.h" + +class AutoPVDelete { +public: + AutoPVDelete(PVJpgDecoderInterface* codec) : fCodec(codec) {} + ~AutoPVDelete() { + fCodec->Reset(); + PVJpgDecoderFactory::DeletePVJpgDecoder(fCodec); + } +private: + PVJpgDecoderInterface* fCodec; +}; + +class MyObserver : public MPVJpegDecObserver { +public: + MyObserver() : fCount(0) {} + ~MyObserver() { + if (fCount != 0) { + SkDebugf("--- pvjpeg left %d allocations\n", fCount); + } + } + + virtual void allocateBuffer(uint8* &buffer, int32 buffersize) { + ++fCount; + // we double the allocation to work around bug when height is odd + buffer = (uint8*)sk_malloc_throw(buffersize << 1); + SkDebugf("--- pvjpeg alloc [%d] %d addr=%p\n", fCount, buffersize, buffer); + } + + virtual void deallocateBuffer(uint8 *buffer) { + SkDebugf("--- pvjpeg free [%d] addr=%p\n", fCount, buffer); + --fCount; + sk_free(buffer); + } + +private: + int fCount; +}; + +static void check_status(TPvJpgDecStatus status) { + if (TPVJPGDEC_SUCCESS != status) { + SkDEBUGF(("--- pvjpeg status %d\n", status)); + } +} + +static bool getFrame(PVJpgDecoderInterface* codec, SkBitmap* bitmap, + SkBitmap::Config prefConfig, SkImageDecoder::Mode mode) { + TPvJpgDecInfo info; + TPvJpgDecStatus status = codec->GetInfo(&info); + if (status != TPVJPGDEC_SUCCESS) + return false; + + int width = info.iWidth[0]; + int height = info.iHeight[0]; + + bitmap->setConfig(SkBitmap::kRGB_565_Config, width, height); + bitmap->setIsOpaque(true); + + if (SkImageDecoder::kDecodeBounds_Mode == mode) { + return true; + } + + SkASSERT(info.iNumComponent == 3); + + TPvJpgDecOutputFmt format; + format.iColorFormat = TPV_COLORFMT_RGB16; + format.iCropped.topLeftX = 0; + format.iCropped.topLeftY = 0; + format.iCropped.bottomRightX = width - 1; + format.iCropped.bottomRightY = height - 1; + format.iOutputPitch = bitmap->rowBytes() >> 1; + status = codec->SetOutput(&format); + if (status != TPVJPGDEC_SUCCESS) { + SkDebugf("--- PV SetOutput failed %d\n", status); + return false; + } + + TPvJpgDecFrame frame; + uint8* ptrs[3]; + int32 widths[3], heights[3]; + bzero(ptrs, sizeof(ptrs)); + frame.ptr = ptrs; + frame.iWidth = widths; + frame.iHeight = heights; + + status = codec->GetFrame(&frame); + if (status != TPVJPGDEC_SUCCESS) { + SkDebugf("--- PV GetFrame failed %d\n", status); + return false; + } + + bitmap->allocPixels(); + memcpy(bitmap->getPixels(), ptrs[0], bitmap->getSize()); + return true; +} + +class OsclCleanupper { +public: + OsclCleanupper() { + OsclBase::Init(); + OsclErrorTrap::Init(); + OsclMem::Init(); + } + ~OsclCleanupper() { + OsclMem::Cleanup(); + OsclErrorTrap::Cleanup(); + OsclBase::Cleanup(); + } +}; + +bool SkPVJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap, + SkBitmap::Config prefConfig, Mode mode) +{ + // do I need this guy? + OsclCleanupper oc; + + PVJpgDecoderInterface* codec = PVJpgDecoderFactory::CreatePVJpgDecoder(); + TPvJpgDecStatus status = codec->Init(); + check_status(status); + + MyObserver observer; // must create before autopvdelete + AutoPVDelete ad(codec); + + status = codec->SetObserver(&observer); + check_status(status); + + char* storage = fStorage; + int32 bytesInStorage = 0; + for (;;) + { + int32 bytesRead = stream->read(storage + bytesInStorage, + STORAGE_SIZE - bytesInStorage); + if (bytesRead <= 0) { + SkDEBUGF(("SkPVJPEGImageDecoder: stream read returned %d\n", bytesRead)); + return false; + } + + // update bytesInStorage to account for the read() + bytesInStorage += bytesRead; + SkASSERT(bytesInStorage <= STORAGE_SIZE); + + // now call Decode to eat some of the bytes + int32 consumed = bytesInStorage; + status = codec->Decode((uint8*)storage, &consumed); + + SkASSERT(bytesInStorage >= consumed); + bytesInStorage -= consumed; + // now bytesInStorage is the remaining unread bytes + if (bytesInStorage > 0) { // slide the leftovers to the beginning + SkASSERT(storage == fStorage); + SkASSERT(consumed >= 0 && bytesInStorage >= 0); + SkASSERT((size_t)(consumed + bytesInStorage) <= sizeof(fStorage)); + SkASSERT(sizeof(fStorage) == STORAGE_SIZE); + // SkDebugf("-- memmov srcOffset=%d, numBytes=%d\n", consumed, bytesInStorage); + memmove(storage, storage + consumed, bytesInStorage); + } + + switch (status) { + case TPVJPGDEC_SUCCESS: + SkDEBUGF(("SkPVJPEGImageDecoder::Decode returned success?\n");) + return false; + case TPVJPGDEC_FRAME_READY: + case TPVJPGDEC_DONE: + return getFrame(codec, decodedBitmap, prefConfig, mode); + case TPVJPGDEC_FAIL: + case TPVJPGDEC_INVALID_MEMORY: + case TPVJPGDEC_INVALID_PARAMS: + case TPVJPGDEC_NO_IMAGE_DATA: + SkDEBUGF(("SkPVJPEGImageDecoder: failed to decode err=%d\n", status);) + return false; + case TPVJPGDEC_WAITING_FOR_INPUT: + break; // loop around and eat more from the stream + } + } + return false; +} + diff --git a/src/images/SkImageDecoder_wbmp.cpp b/src/images/SkImageDecoder_wbmp.cpp new file mode 100644 index 0000000000..9d188f6016 --- /dev/null +++ b/src/images/SkImageDecoder_wbmp.cpp @@ -0,0 +1,167 @@ +/** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "SkImageDecoder.h" +#include "SkColor.h" +#include "SkColorPriv.h" +#include "SkMath.h" +#include "SkStream.h" +#include "SkTemplates.h" +#include "SkUtils.h" + +class SkWBMPImageDecoder : public SkImageDecoder { +public: + virtual Format getFormat() const { + return kWBMP_Format; + } + +protected: + virtual bool onDecode(SkStream* stream, SkBitmap* bm, + SkBitmap::Config pref, Mode); +}; + +static bool read_byte(SkStream* stream, uint8_t* data) +{ + return stream->read(data, 1) == 1; +} + +static bool read_mbf(SkStream* stream, int* value) +{ + int n = 0; + uint8_t data; + do { + if (!read_byte(stream, &data)) { + return false; + } + n = (n << 7) | (data & 0x7F); + } while (data & 0x80); + + *value = n; + return true; +} + +struct wbmp_head { + int fWidth; + int fHeight; + + bool init(SkStream* stream) + { + uint8_t data; + + if (!read_byte(stream, &data) || data != 0) { // unknown type + return false; + } + if (!read_byte(stream, &data) || (data & 0x9F)) { // skip fixed header + return false; + } + if (!read_mbf(stream, &fWidth) || (unsigned)fWidth > 0xFFFF) { + return false; + } + if (!read_mbf(stream, &fHeight) || (unsigned)fHeight > 0xFFFF) { + return false; + } + return fWidth != 0 && fHeight != 0; + } +}; + +SkImageDecoder* SkImageDecoder_WBMP_Factory(SkStream* stream) +{ + wbmp_head head; + + if (head.init(stream)) { + return SkNEW(SkWBMPImageDecoder); + } + return NULL; +} + +static void expand_bits_to_bytes(uint8_t dst[], const uint8_t src[], int bits) +{ + int bytes = bits >> 3; + + for (int i = 0; i < bytes; i++) { + unsigned mask = *src++; + dst[0] = (mask >> 7) & 1; + dst[1] = (mask >> 6) & 1; + dst[2] = (mask >> 5) & 1; + dst[3] = (mask >> 4) & 1; + dst[4] = (mask >> 3) & 1; + dst[5] = (mask >> 2) & 1; + dst[6] = (mask >> 1) & 1; + dst[7] = (mask >> 0) & 1; + dst += 8; + } + + bits &= 7; + if (bits > 0) { + unsigned mask = *src; + do { + *dst++ = (mask >> 7) & 1;; + mask <<= 1; + } while (--bits != 0); + } +} + +#define SkAlign8(x) (((x) + 7) & ~7) + +bool SkWBMPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap, + SkBitmap::Config prefConfig, Mode mode) +{ + wbmp_head head; + + if (!head.init(stream)) { + return false; + } + + int width = head.fWidth; + int height = head.fHeight; + + // assign these directly, in case we return kDimensions_Result + decodedBitmap->setConfig(SkBitmap::kIndex8_Config, width, height); + decodedBitmap->setIsOpaque(true); + + if (SkImageDecoder::kDecodeBounds_Mode == mode) + return true; + + const SkPMColor colors[] = { SK_ColorBLACK, SK_ColorWHITE }; + SkColorTable* ct = SkNEW_ARGS(SkColorTable, (colors, 2)); + SkAutoUnref aur(ct); + + if (!this->allocPixelRef(decodedBitmap, ct)) { + return false; + } + + SkAutoLockPixels alp(*decodedBitmap); + + uint8_t* dst = decodedBitmap->getAddr8(0, 0); + // store the 1-bit valuess at the end of our pixels, so we won't stomp + // on them before we're read them. Just trying to avoid a temp allocation + size_t srcRB = SkAlign8(width) >> 3; + size_t srcSize = height * srcRB; + uint8_t* src = dst + decodedBitmap->getSize() - srcSize; + if (stream->read(src, srcSize) != srcSize) { + return false; + } + + for (int y = 0; y < height; y++) + { + expand_bits_to_bytes(dst, src, width); + dst += decodedBitmap->rowBytes(); + src += srcRB; + } + + return true; +} + diff --git a/src/images/SkImageRef.cpp b/src/images/SkImageRef.cpp new file mode 100644 index 0000000000..90c37b65e1 --- /dev/null +++ b/src/images/SkImageRef.cpp @@ -0,0 +1,165 @@ +#include "SkImageRef.h" +#include "SkBitmap.h" +#include "SkFlattenable.h" +#include "SkImageDecoder.h" +#include "SkStream.h" +#include "SkTemplates.h" +#include "SkThread.h" + +// can't be static, as SkImageRef_Pool needs to see it +SkMutex gImageRefMutex; + +/////////////////////////////////////////////////////////////////////////////// + +SkImageRef::SkImageRef(SkStream* stream, SkBitmap::Config config, + int sampleSize) + : SkPixelRef(&gImageRefMutex), fErrorInDecoding(false) { + SkASSERT(stream); + SkASSERT(1 == stream->getRefCnt()); + + fStream = stream; + fConfig = config; + fSampleSize = sampleSize; + fPrev = fNext = NULL; + +#ifdef DUMP_IMAGEREF_LIFECYCLE + SkDebugf("add ImageRef %p [%d] data=%d\n", + this, config, (int)stream->getLength()); +#endif +} + +SkImageRef::~SkImageRef() { + SkASSERT(&gImageRefMutex == this->mutex()); + +#ifdef DUMP_IMAGEREF_LIFECYCLE + SkDebugf("delete ImageRef %p [%d] data=%d\n", + this, fConfig, (int)fStream->getLength()); +#endif + + delete fStream; +} + +bool SkImageRef::getInfo(SkBitmap* bitmap) { + SkAutoMutexAcquire ac(gImageRefMutex); + + if (!this->prepareBitmap(SkImageDecoder::kDecodeBounds_Mode)) { + return false; + } + + SkASSERT(SkBitmap::kNo_Config != fBitmap.config()); + if (bitmap) { + bitmap->setConfig(fBitmap.config(), fBitmap.width(), fBitmap.height()); + } + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool SkImageRef::onDecode(SkImageDecoder* codec, SkStream* stream, + SkBitmap* bitmap, SkBitmap::Config config, + SkImageDecoder::Mode mode) { + return codec->decode(stream, bitmap, config, mode); +} + +bool SkImageRef::prepareBitmap(SkImageDecoder::Mode mode) { + SkASSERT(&gImageRefMutex == this->mutex()); + + if (fErrorInDecoding) { + return false; + } + + /* As soon as we really know our config, we record it, so that on + subsequent calls to the codec, we are sure we will always get the same + result. + */ + if (SkBitmap::kNo_Config != fBitmap.config()) { + fConfig = fBitmap.config(); + } + + if (NULL != fBitmap.getPixels() || + (SkBitmap::kNo_Config != fBitmap.config() && + SkImageDecoder::kDecodeBounds_Mode == mode)) { + return true; + } + + SkASSERT(fBitmap.getPixels() == NULL); + + fStream->rewind(); + + SkImageDecoder* codec = SkImageDecoder::Factory(fStream); + if (codec) { + SkAutoTDelete<SkImageDecoder> ad(codec); + + codec->setSampleSize(fSampleSize); + if (this->onDecode(codec, fStream, &fBitmap, fConfig, mode)) { + return true; + } + } + +#ifdef DUMP_IMAGEREF_LIFECYCLE + if (NULL == codec) { + SkDebugf("--- ImageRef: <%s> failed to find codec\n", this->getURI()); + } else { + SkDebugf("--- ImageRef: <%s> failed in codec for %d mode\n", + this->getURI(), mode); + } +#endif + fErrorInDecoding = true; + fBitmap.reset(); + return false; +} + +void* SkImageRef::onLockPixels(SkColorTable** ct) { + SkASSERT(&gImageRefMutex == this->mutex()); + + if (NULL == fBitmap.getPixels()) { + (void)this->prepareBitmap(SkImageDecoder::kDecodePixels_Mode); + } + + if (ct) { + *ct = fBitmap.getColorTable(); + } + return fBitmap.getPixels(); +} + +void SkImageRef::onUnlockPixels() { + // we're already have the mutex locked + SkASSERT(&gImageRefMutex == this->mutex()); +} + +size_t SkImageRef::ramUsed() const { + size_t size = 0; + + if (fBitmap.getPixels()) { + size = fBitmap.getSize(); + if (fBitmap.getColorTable()) { + size += fBitmap.getColorTable()->count() * sizeof(SkPMColor); + } + } + return size; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkImageRef::SkImageRef(SkFlattenableReadBuffer& buffer) + : INHERITED(buffer, &gImageRefMutex), fErrorInDecoding(false) { + fConfig = (SkBitmap::Config)buffer.readU8(); + fSampleSize = buffer.readU8(); + size_t length = buffer.readU32(); + fStream = SkNEW_ARGS(SkMemoryStream, (length)); + buffer.read((void*)fStream->getMemoryBase(), length); + + fPrev = fNext = NULL; +} + +void SkImageRef::flatten(SkFlattenableWriteBuffer& buffer) const { + this->INHERITED::flatten(buffer); + + buffer.write8(fConfig); + buffer.write8(fSampleSize); + size_t length = fStream->getLength(); + buffer.write32(length); + fStream->rewind(); + buffer.readFromStream(fStream, length); +} + diff --git a/src/images/SkImageRefPool.cpp b/src/images/SkImageRefPool.cpp new file mode 100644 index 0000000000..e322507d87 --- /dev/null +++ b/src/images/SkImageRefPool.cpp @@ -0,0 +1,186 @@ +#include "SkImageRefPool.h" +#include "SkImageRef.h" +#include "SkThread.h" + +SkImageRefPool::SkImageRefPool() { + fRAMBudget = 0; // means no explicit limit + fRAMUsed = 0; + fCount = 0; + fHead = fTail = NULL; +} + +SkImageRefPool::~SkImageRefPool() { + // SkASSERT(NULL == fHead); +} + +void SkImageRefPool::setRAMBudget(size_t size) { + if (fRAMBudget != size) { + fRAMBudget = size; + this->purgeIfNeeded(); + } +} + +void SkImageRefPool::justAddedPixels(SkImageRef* ref) { +#ifdef DUMP_IMAGEREF_LIFECYCLE + SkDebugf("=== ImagePool: add pixels %s [%d %d %d] bytes=%d heap=%d\n", + ref->getURI(), + ref->fBitmap.width(), ref->fBitmap.height(), + ref->fBitmap.bytesPerPixel(), + ref->fBitmap.getSize(), (int)fRAMUsed); +#endif + fRAMUsed += ref->ramUsed(); + this->purgeIfNeeded(); +} + +void SkImageRefPool::canLosePixels(SkImageRef* ref) { + // the refs near fHead have recently been released (used) + // if we purge, we purge from the tail + this->detach(ref); + this->addToHead(ref); + this->purgeIfNeeded(); +} + +void SkImageRefPool::purgeIfNeeded() { + // do nothing if we have a zero-budget (i.e. unlimited) + if (fRAMBudget != 0) { + this->setRAMUsed(fRAMBudget); + } +} + +void SkImageRefPool::setRAMUsed(size_t limit) { + SkImageRef* ref = fTail; + + while (NULL != ref && fRAMUsed > limit) { + // only purge it if its pixels are unlocked + if (0 == ref->getLockCount() && ref->fBitmap.getPixels()) { + size_t size = ref->ramUsed(); + SkASSERT(size <= fRAMUsed); + fRAMUsed -= size; + +#ifdef DUMP_IMAGEREF_LIFECYCLE + SkDebugf("=== ImagePool: purge %s [%d %d %d] bytes=%d heap=%d\n", + ref->getURI(), + ref->fBitmap.width(), ref->fBitmap.height(), + ref->fBitmap.bytesPerPixel(), + (int)size, (int)fRAMUsed); +#endif + + // remember the bitmap config (don't call reset), + // just clear the pixel memory + ref->fBitmap.setPixels(NULL); + SkASSERT(NULL == ref->fBitmap.getPixels()); + } + ref = ref->fPrev; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void SkImageRefPool::addToHead(SkImageRef* ref) { + ref->fNext = fHead; + ref->fPrev = NULL; + + if (fHead) { + SkASSERT(NULL == fHead->fPrev); + fHead->fPrev = ref; + } + fHead = ref; + + if (NULL == fTail) { + fTail = ref; + } + fCount += 1; + SkASSERT(computeCount() == fCount); + + fRAMUsed += ref->ramUsed(); +} + +void SkImageRefPool::addToTail(SkImageRef* ref) { + ref->fNext = NULL; + ref->fPrev = fTail; + + if (fTail) { + SkASSERT(NULL == fTail->fNext); + fTail->fNext = ref; + } + fTail = ref; + + if (NULL == fHead) { + fHead = ref; + } + fCount += 1; + SkASSERT(computeCount() == fCount); + + fRAMUsed += ref->ramUsed(); +} + +void SkImageRefPool::detach(SkImageRef* ref) { + SkASSERT(fCount > 0); + + if (fHead == ref) { + fHead = ref->fNext; + } + if (fTail == ref) { + fTail = ref->fPrev; + } + if (ref->fPrev) { + ref->fPrev->fNext = ref->fNext; + } + if (ref->fNext) { + ref->fNext->fPrev = ref->fPrev; + } + + ref->fNext = ref->fPrev = NULL; + + fCount -= 1; + SkASSERT(computeCount() == fCount); + + SkASSERT(fRAMUsed >= ref->ramUsed()); + fRAMUsed -= ref->ramUsed(); +} + +int SkImageRefPool::computeCount() const { + SkImageRef* ref = fHead; + int count = 0; + + while (ref != NULL) { + count += 1; + ref = ref->fNext; + } + +#ifdef SK_DEBUG + ref = fTail; + int count2 = 0; + + while (ref != NULL) { + count2 += 1; + ref = ref->fPrev; + } + SkASSERT(count2 == count); +#endif + + return count; +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "SkStream.h" + +void SkImageRefPool::dump() const { +#if defined(SK_DEBUG) || defined(DUMP_IMAGEREF_LIFECYCLE) + SkDebugf("ImagePool dump: bugdet: %d used: %d count: %d\n", + (int)fRAMBudget, (int)fRAMUsed, fCount); + + SkImageRef* ref = fHead; + + while (ref != NULL) { + SkDebugf(" [%3d %3d %d] ram=%d data=%d locks=%d %s\n", ref->fBitmap.width(), + ref->fBitmap.height(), ref->fBitmap.config(), + ref->ramUsed(), (int)ref->fStream->getLength(), + ref->getLockCount(), ref->getURI()); + + ref = ref->fNext; + } +#endif +} + diff --git a/src/images/SkImageRefPool.h b/src/images/SkImageRefPool.h new file mode 100644 index 0000000000..b2eb7b3b1b --- /dev/null +++ b/src/images/SkImageRefPool.h @@ -0,0 +1,43 @@ +#ifndef SkImageRefPool_DEFINED +#define SkImageRefPool_DEFINED + +#include "SkTypes.h" + +class SkImageRef; +class SkImageRef_GlobalPool; + +class SkImageRefPool { +public: + SkImageRefPool(); + ~SkImageRefPool(); + + size_t getRAMBudget() const { return fRAMBudget; } + void setRAMBudget(size_t); + + size_t getRAMUsed() const { return fRAMUsed; } + void setRAMUsed(size_t limit); + + void addToHead(SkImageRef*); + void addToTail(SkImageRef*); + void detach(SkImageRef*); + + void dump() const; + +private: + size_t fRAMBudget; + size_t fRAMUsed; + + int fCount; + SkImageRef* fHead, *fTail; + + int computeCount() const; + + friend class SkImageRef_GlobalPool; + + void justAddedPixels(SkImageRef*); + void canLosePixels(SkImageRef*); + void purgeIfNeeded(); +}; + +#endif + diff --git a/src/images/SkImageRef_GlobalPool.cpp b/src/images/SkImageRef_GlobalPool.cpp new file mode 100644 index 0000000000..1f0bc4306b --- /dev/null +++ b/src/images/SkImageRef_GlobalPool.cpp @@ -0,0 +1,83 @@ +#include "SkImageRef_GlobalPool.h" +#include "SkImageRefPool.h" +#include "SkThread.h" + +extern SkMutex gImageRefMutex; + +static SkImageRefPool gGlobalImageRefPool; + +SkImageRef_GlobalPool::SkImageRef_GlobalPool(SkStream* stream, + SkBitmap::Config config, + int sampleSize) + : SkImageRef(stream, config, sampleSize) { + this->mutex()->acquire(); + gGlobalImageRefPool.addToHead(this); + this->mutex()->release(); +} + +SkImageRef_GlobalPool::~SkImageRef_GlobalPool() { + this->mutex()->acquire(); + gGlobalImageRefPool.detach(this); + this->mutex()->release(); +} + +bool SkImageRef_GlobalPool::onDecode(SkImageDecoder* codec, SkStream* stream, + SkBitmap* bitmap, SkBitmap::Config config, + SkImageDecoder::Mode mode) { + if (!this->INHERITED::onDecode(codec, stream, bitmap, config, mode)) { + return false; + } + if (mode == SkImageDecoder::kDecodePixels_Mode) { + gGlobalImageRefPool.justAddedPixels(this); + } + return true; +} + +void SkImageRef_GlobalPool::onUnlockPixels() { + this->INHERITED::onUnlockPixels(); + + gGlobalImageRefPool.canLosePixels(this); +} + +SkImageRef_GlobalPool::SkImageRef_GlobalPool(SkFlattenableReadBuffer& buffer) + : INHERITED(buffer) { + this->mutex()->acquire(); + gGlobalImageRefPool.addToHead(this); + this->mutex()->release(); +} + +SkPixelRef* SkImageRef_GlobalPool::Create(SkFlattenableReadBuffer& buffer) { + return SkNEW_ARGS(SkImageRef_GlobalPool, (buffer)); +} + +static SkPixelRef::Registrar::Registrar reg("SkImageRef_GlobalPool", + SkImageRef_GlobalPool::Create); + +/////////////////////////////////////////////////////////////////////////////// +// global imagerefpool wrappers + +size_t SkImageRef_GlobalPool::GetRAMBudget() { + SkAutoMutexAcquire ac(gImageRefMutex); + return gGlobalImageRefPool.getRAMBudget(); +} + +void SkImageRef_GlobalPool::SetRAMBudget(size_t size) { + SkAutoMutexAcquire ac(gImageRefMutex); + gGlobalImageRefPool.setRAMBudget(size); +} + +size_t SkImageRef_GlobalPool::GetRAMUsed() { + SkAutoMutexAcquire ac(gImageRefMutex); + return gGlobalImageRefPool.getRAMUsed(); +} + +void SkImageRef_GlobalPool::SetRAMUsed(size_t usage) { + SkAutoMutexAcquire ac(gImageRefMutex); + gGlobalImageRefPool.setRAMUsed(usage); +} + +void SkImageRef_GlobalPool::DumpPool() { + SkAutoMutexAcquire ac(gImageRefMutex); + gGlobalImageRefPool.dump(); +} + diff --git a/src/images/SkMMapStream.cpp b/src/images/SkMMapStream.cpp new file mode 100644 index 0000000000..2aee9451c3 --- /dev/null +++ b/src/images/SkMMapStream.cpp @@ -0,0 +1,63 @@ +#include "SkMMapStream.h" + +#include <unistd.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <errno.h> + +SkMMAPStream::SkMMAPStream(const char filename[]) +{ + fFildes = -1; // initialize to failure case + + int fildes = open(filename, O_RDONLY); + if (fildes < 0) + { + SkDEBUGF(("---- failed to open(%s) for mmap stream error=%d\n", filename, errno)); + return; + } + + off_t size = lseek(fildes, 0, SEEK_END); // find the file size + if (size == -1) + { + SkDEBUGF(("---- failed to lseek(%s) for mmap stream error=%d\n", filename, errno)); + close(fildes); + return; + } + (void)lseek(fildes, 0, SEEK_SET); // restore file offset to beginning + + void* addr = mmap(NULL, size, PROT_READ, MAP_SHARED, fildes, 0); + if (MAP_FAILED == addr) + { + SkDEBUGF(("---- failed to mmap(%s) for mmap stream error=%d\n", filename, errno)); + close(fildes); + return; + } + + this->INHERITED::setMemory(addr, size); + + fFildes = fildes; + fAddr = addr; + fSize = size; +} + +SkMMAPStream::~SkMMAPStream() +{ + this->closeMMap(); +} + +void SkMMAPStream::setMemory(const void* data, size_t length) +{ + this->closeMMap(); + this->INHERITED::setMemory(data, length); +} + +void SkMMAPStream::closeMMap() +{ + if (fFildes >= 0) + { + munmap(fAddr, fSize); + close(fFildes); + fFildes = -1; + } +} + diff --git a/src/images/SkMovie.cpp b/src/images/SkMovie.cpp new file mode 100644 index 0000000000..7186ed51b5 --- /dev/null +++ b/src/images/SkMovie.cpp @@ -0,0 +1,101 @@ +#include "SkMovie.h" +#include "SkCanvas.h" +#include "SkPaint.h" + +// We should never see this in normal operation since our time values are +// 0-based. So we use it as a sentinal. +#define UNINITIALIZED_MSEC ((SkMSec)-1) + +SkMovie::SkMovie() +{ + fInfo.fDuration = UNINITIALIZED_MSEC; // uninitialized + fCurrTime = UNINITIALIZED_MSEC; // uninitialized + fNeedBitmap = true; +} + +void SkMovie::ensureInfo() +{ + if (fInfo.fDuration == UNINITIALIZED_MSEC && !this->onGetInfo(&fInfo)) + memset(&fInfo, 0, sizeof(fInfo)); // failure +} + +SkMSec SkMovie::duration() +{ + this->ensureInfo(); + return fInfo.fDuration; +} + +int SkMovie::width() +{ + this->ensureInfo(); + return fInfo.fWidth; +} + +int SkMovie::height() +{ + this->ensureInfo(); + return fInfo.fHeight; +} + +int SkMovie::isOpaque() +{ + this->ensureInfo(); + return fInfo.fIsOpaque; +} + +bool SkMovie::setTime(SkMSec time) +{ + SkMSec dur = this->duration(); + if (time > dur) + time = dur; + + bool changed = false; + if (time != fCurrTime) + { + fCurrTime = time; + changed = this->onSetTime(time); + fNeedBitmap |= changed; + } + return changed; +} + +const SkBitmap& SkMovie::bitmap() +{ + if (fCurrTime == UNINITIALIZED_MSEC) // uninitialized + this->setTime(0); + + if (fNeedBitmap) + { + if (!this->onGetBitmap(&fBitmap)) // failure + fBitmap.reset(); + fNeedBitmap = false; + } + return fBitmap; +} + +//////////////////////////////////////////////////////////////////// + +#include "SkStream.h" + +SkMovie* SkMovie::DecodeMemory(const void* data, size_t length) { + SkMemoryStream stream(data, length, false); + return SkMovie::DecodeStream(&stream); +} + +SkMovie* SkMovie::DecodeFile(const char path[]) +{ + SkMovie* movie = NULL; + + SkFILEStream stream(path); + if (stream.isValid()) { + movie = SkMovie::DecodeStream(&stream); + } +#ifdef SK_DEBUG + else { + SkDebugf("Movie file not found <%s>\n", path); + } +#endif + + return movie; +} + diff --git a/src/images/SkMovie_gif.cpp b/src/images/SkMovie_gif.cpp new file mode 100644 index 0000000000..ca9c812712 --- /dev/null +++ b/src/images/SkMovie_gif.cpp @@ -0,0 +1,224 @@ +/* libs/graphics/images/SkImageDecoder_libgif.cpp +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "SkMovie.h" +#include "SkColor.h" +#include "SkColorPriv.h" +#include "SkStream.h" +#include "SkTemplates.h" + +#include "gif_lib.h" + +class SkGIFMovie : public SkMovie { +public: + SkGIFMovie(SkStream* stream); + virtual ~SkGIFMovie(); + +protected: + virtual bool onGetInfo(Info*); + virtual bool onSetTime(SkMSec); + virtual bool onGetBitmap(SkBitmap*); + +private: + GifFileType* fGIF; + SavedImage* fCurrSavedImage; +}; + +SkMovie* SkMovie_GIF_Factory(SkStream* stream) { + char buf[GIF_STAMP_LEN]; + if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) { + if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 || + memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 || + memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) { + stream->rewind(); + return SkNEW_ARGS(SkGIFMovie, (stream)); + } + } + return NULL; +} + +static int Decode(GifFileType* fileType, GifByteType* out, int size) { + SkStream* stream = (SkStream*) fileType->UserData; + return (int) stream->read(out, size); +} + +SkGIFMovie::SkGIFMovie(SkStream* stream) +{ + fGIF = DGifOpen( stream, Decode ); + if (NULL == fGIF) + return; + + if (DGifSlurp(fGIF) != GIF_OK) + { + DGifCloseFile(fGIF); + fGIF = NULL; + } + fCurrSavedImage = NULL; +} + +SkGIFMovie::~SkGIFMovie() +{ + if (fGIF) + DGifCloseFile(fGIF); +} + +static SkMSec savedimage_duration(const SavedImage* image) +{ + for (int j = 0; j < image->ExtensionBlockCount; j++) + { + if (image->ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE) + { + int size = image->ExtensionBlocks[j].ByteCount; + SkASSERT(size >= 4); + const uint8_t* b = (const uint8_t*)image->ExtensionBlocks[j].Bytes; + return ((b[2] << 8) | b[1]) * 10; + } + } + return 0; +} + +bool SkGIFMovie::onGetInfo(Info* info) +{ + if (NULL == fGIF) + return false; + + SkMSec dur = 0; + for (int i = 0; i < fGIF->ImageCount; i++) + dur += savedimage_duration(&fGIF->SavedImages[i]); + + info->fDuration = dur; + info->fWidth = fGIF->SWidth; + info->fHeight = fGIF->SHeight; + info->fIsOpaque = false; // how to compute? + return true; +} + +bool SkGIFMovie::onSetTime(SkMSec time) +{ + if (NULL == fGIF) + return false; + + SkMSec dur = 0; + for (int i = 0; i < fGIF->ImageCount; i++) + { + dur += savedimage_duration(&fGIF->SavedImages[i]); + if (dur >= time) + { + SavedImage* prev = fCurrSavedImage; + fCurrSavedImage = &fGIF->SavedImages[i]; + return prev != fCurrSavedImage; + } + } + fCurrSavedImage = &fGIF->SavedImages[fGIF->ImageCount - 1]; + return true; +} + +bool SkGIFMovie::onGetBitmap(SkBitmap* bm) +{ + GifFileType* gif = fGIF; + if (NULL == gif) + return false; + + // should we check for the Image cmap or the global (SColorMap) first? + ColorMapObject* cmap = gif->SColorMap; + if (cmap == NULL) + cmap = gif->Image.ColorMap; + + if (cmap == NULL || gif->ImageCount < 1 || cmap->ColorCount != (1 << cmap->BitsPerPixel)) + { + SkASSERT(!"bad colortable setup"); + return false; + } + + const int width = gif->SWidth; + const int height = gif->SHeight; + if (width <= 0 || height <= 0) { + return false; + } + + SavedImage* gif_image = fCurrSavedImage; + SkBitmap::Config config = SkBitmap::kIndex8_Config; + + SkColorTable* colorTable = SkNEW_ARGS(SkColorTable, (cmap->ColorCount)); + SkAutoUnref aur(colorTable); + + bm->setConfig(config, width, height, 0); + if (!bm->allocPixels(colorTable)) { + return false; + } + + int transparent = -1; + for (int i = 0; i < gif_image->ExtensionBlockCount; ++i) { + ExtensionBlock* eb = gif_image->ExtensionBlocks + i; + if (eb->Function == 0xF9 && + eb->ByteCount == 4) { + bool has_transparency = ((eb->Bytes[0] & 1) == 1); + if (has_transparency) { + transparent = (unsigned char)eb->Bytes[3]; + } + } + } + + SkPMColor* colorPtr = colorTable->lockColors(); + + if (transparent >= 0) + memset(colorPtr, 0, cmap->ColorCount * 4); + else + colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag); + + for (int index = 0; index < cmap->ColorCount; index++) + { + if (transparent != index) + colorPtr[index] = SkPackARGB32(0xFF, cmap->Colors[index].Red, + cmap->Colors[index].Green, cmap->Colors[index].Blue); + } + colorTable->unlockColors(true); + + unsigned char* in = (unsigned char*)gif_image->RasterBits; + unsigned char* out = bm->getAddr8(0, 0); + if (gif->Image.Interlace) { + + // deinterlace + int row; + // group 1 - every 8th row, starting with row 0 + for (row = 0; row < height; row += 8) { + memcpy(out + width * row, in, width); + in += width; + } + + // group 2 - every 8th row, starting with row 4 + for (row = 4; row < height; row += 8) { + memcpy(out + width * row, in, width); + in += width; + } + + // group 3 - every 4th row, starting with row 2 + for (row = 2; row < height; row += 4) { + memcpy(out + width * row, in, width); + in += width; + } + + for (row = 1; row < height; row += 2) { + memcpy(out + width * row, in, width); + in += width; + } + + } else { + memcpy(out, in, width * height); + } + return true; +} diff --git a/src/images/SkPageFlipper.cpp b/src/images/SkPageFlipper.cpp new file mode 100644 index 0000000000..526ba093d6 --- /dev/null +++ b/src/images/SkPageFlipper.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkPageFlipper.h" + +SkPageFlipper::SkPageFlipper() { + fWidth = 0; + fHeight = 0; + fDirty0 = &fDirty0Storage; + fDirty1 = &fDirty1Storage; + + fDirty0->setEmpty(); + fDirty1->setEmpty(); +} + +SkPageFlipper::SkPageFlipper(int width, int height) { + fWidth = width; + fHeight = height; + fDirty0 = &fDirty0Storage; + fDirty1 = &fDirty1Storage; + + fDirty0->setRect(0, 0, width, height); + fDirty1->setEmpty(); +} + +void SkPageFlipper::resize(int width, int height) { + fWidth = width; + fHeight = height; + + // this is the opposite of the constructors + fDirty1->setRect(0, 0, width, height); + fDirty0->setEmpty(); +} + +void SkPageFlipper::inval() { + fDirty1->setRect(0, 0, fWidth, fHeight); +} + +void SkPageFlipper::inval(const SkIRect& rect) { + SkIRect r; + r.set(0, 0, fWidth, fHeight); + if (r.intersect(rect)) { + fDirty1->op(r, SkRegion::kUnion_Op); + } +} + +void SkPageFlipper::inval(const SkRegion& rgn) { + SkRegion r; + r.setRect(0, 0, fWidth, fHeight); + if (r.op(rgn, SkRegion::kIntersect_Op)) { + fDirty1->op(r, SkRegion::kUnion_Op); + } +} + +void SkPageFlipper::inval(const SkRect& rect, bool antialias) { + SkIRect r; + rect.round(&r); + if (antialias) { + r.inset(-1, -1); + } + this->inval(r); +} + +const SkRegion& SkPageFlipper::update(SkRegion* copyBits) { + // Copy over anything new from page0 that isn't dirty in page1 + copyBits->op(*fDirty0, *fDirty1, SkRegion::kDifference_Op); + SkTSwap<SkRegion*>(fDirty0, fDirty1); + fDirty1->setEmpty(); + return *fDirty0; +} + + diff --git a/src/images/SkScaledBitmapSampler.cpp b/src/images/SkScaledBitmapSampler.cpp new file mode 100644 index 0000000000..15f44327a7 --- /dev/null +++ b/src/images/SkScaledBitmapSampler.cpp @@ -0,0 +1,338 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkScaledBitmapSampler.h" +#include "SkBitmap.h" +#include "SkColorPriv.h" +#include "SkDither.h" + +// 8888 + +static bool Sample_Gray_D8888(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int) { + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < width; x++) { + dst[x] = SkPackARGB32(0xFF, src[0], src[0], src[0]); + src += deltaSrc; + } + return false; +} + +static bool Sample_RGBx_D8888(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int) { + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < width; x++) { + dst[x] = SkPackARGB32(0xFF, src[0], src[1], src[2]); + src += deltaSrc; + } + return false; +} + +static bool Sample_RGBA_D8888(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int) { + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + unsigned alphaMask = 0xFF; + for (int x = 0; x < width; x++) { + unsigned alpha = src[3]; + dst[x] = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]); + src += deltaSrc; + alphaMask &= alpha; + } + return alphaMask != 0xFF; +} + +// 565 + +static bool Sample_Gray_D565(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int) { + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + for (int x = 0; x < width; x++) { + dst[x] = SkPack888ToRGB16(src[0], src[0], src[0]); + src += deltaSrc; + } + return false; +} + +static bool Sample_Gray_D565_D(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int y) { + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + DITHER_565_SCAN(y); + for (int x = 0; x < width; x++) { + dst[x] = SkDitherRGBTo565(src[0], src[0], src[0], DITHER_VALUE(x)); + src += deltaSrc; + } + return false; +} + +static bool Sample_RGBx_D565(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int) { + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + for (int x = 0; x < width; x++) { + dst[x] = SkPack888ToRGB16(src[0], src[1], src[2]); + src += deltaSrc; + } + return false; +} + +static bool Sample_RGBx_D565_D(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int y) { + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + DITHER_565_SCAN(y); + for (int x = 0; x < width; x++) { + dst[x] = SkDitherRGBTo565(src[0], src[1], src[2], DITHER_VALUE(x)); + src += deltaSrc; + } + return false; +} + +// 4444 + +static bool Sample_Gray_D4444(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int) { + SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow; + for (int x = 0; x < width; x++) { + unsigned gray = src[0] >> 4; + dst[x] = SkPackARGB4444(0xF, gray, gray, gray); + src += deltaSrc; + } + return false; +} + +static bool Sample_Gray_D4444_D(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int y) { + SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow; + DITHER_4444_SCAN(y); + for (int x = 0; x < width; x++) { + dst[x] = SkDitherARGB32To4444(0xFF, src[0], src[0], src[0], + DITHER_VALUE(x)); + src += deltaSrc; + } + return false; +} + +static bool Sample_RGBx_D4444(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int) { + SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow; + for (int x = 0; x < width; x++) { + dst[x] = SkPackARGB4444(0xF, src[0] >> 4, src[1] >> 4, src[2] >> 4); + src += deltaSrc; + } + return false; +} + +static bool Sample_RGBx_D4444_D(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int y) { + SkPMColor16* dst = (SkPMColor16*)dstRow; + DITHER_4444_SCAN(y); + + for (int x = 0; x < width; x++) { + dst[x] = SkDitherARGB32To4444(0xFF, src[0], src[1], src[2], + DITHER_VALUE(x)); + src += deltaSrc; + } + return false; +} + +static bool Sample_RGBA_D4444(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int) { + SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow; + unsigned alphaMask = 0xFF; + + for (int x = 0; x < width; x++) { + unsigned alpha = src[3]; + SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]); + dst[x] = SkPixel32ToPixel4444(c); + src += deltaSrc; + alphaMask &= alpha; + } + return alphaMask != 0xFF; +} + +static bool Sample_RGBA_D4444_D(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int y) { + SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow; + unsigned alphaMask = 0xFF; + DITHER_4444_SCAN(y); + + for (int x = 0; x < width; x++) { + unsigned alpha = src[3]; + SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]); + dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x)); + src += deltaSrc; + alphaMask &= alpha; + } + return alphaMask != 0xFF; +} + +// Index + +static bool Sample_Index_DI(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int) { + if (1 == deltaSrc) { + memcpy(dstRow, src, width); + } else { + uint8_t* SK_RESTRICT dst = (uint8_t*)dstRow; + for (int x = 0; x < width; x++) { + dst[x] = src[0]; + src += deltaSrc; + } + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "SkScaledBitmapSampler.h" + +SkScaledBitmapSampler::SkScaledBitmapSampler(int width, int height, + int sampleSize) { + if (width <= 0 || height <= 0) { + sk_throw(); + } + + if (sampleSize <= 1) { + fScaledWidth = width; + fScaledHeight = height; + fX0 = fY0 = 0; + fDX = fDY = 1; + return; + } + + int dx = SkMin32(sampleSize, width); + int dy = SkMin32(sampleSize, height); + + fScaledWidth = width / dx; + fScaledHeight = height / dy; + + SkASSERT(fScaledWidth > 0); + SkASSERT(fScaledHeight > 0); + + fX0 = dx >> 1; + fY0 = dy >> 1; + + SkASSERT(fX0 >= 0 && fX0 < width); + SkASSERT(fY0 >= 0 && fY0 < height); + + fDX = dx; + fDY = dy; + + SkASSERT(fDX > 0 && (fX0 + fDX * (fScaledWidth - 1)) < width); + SkASSERT(fDY > 0 && (fY0 + fDY * (fScaledHeight - 1)) < height); + + fRowProc = NULL; +} + +bool SkScaledBitmapSampler::begin(SkBitmap* dst, SrcConfig sc, bool dither) { + static const RowProc gProcs[] = { + // 8888 (no dither distinction) + Sample_Gray_D8888, Sample_Gray_D8888, + Sample_RGBx_D8888, Sample_RGBx_D8888, + Sample_RGBA_D8888, Sample_RGBA_D8888, + NULL, NULL, + // 565 (no alpha distinction) + Sample_Gray_D565, Sample_Gray_D565_D, + Sample_RGBx_D565, Sample_RGBx_D565_D, + Sample_RGBx_D565, Sample_RGBx_D565_D, + NULL, NULL, + // 4444 + Sample_Gray_D4444, Sample_Gray_D4444_D, + Sample_RGBx_D4444, Sample_RGBx_D4444_D, + Sample_RGBA_D4444, Sample_RGBA_D4444_D, + NULL, NULL, + // Index8 + NULL, NULL, + NULL, NULL, + NULL, NULL, + Sample_Index_DI, Sample_Index_DI, + }; + + + int index = 0; + if (dither) { + index += 1; + } + switch (sc) { + case SkScaledBitmapSampler::kGray: + fSrcPixelSize = 1; + index += 0; + break; + case SkScaledBitmapSampler::kRGB: + fSrcPixelSize = 3; + index += 2; + break; + case SkScaledBitmapSampler::kRGBX: + fSrcPixelSize = 4; + index += 2; + break; + case SkScaledBitmapSampler::kRGBA: + fSrcPixelSize = 4; + index += 4; + break; + case SkScaledBitmapSampler::kIndex: + fSrcPixelSize = 1; + index += 6; + break; + default: + return false; + } + + switch (dst->config()) { + case SkBitmap::kARGB_8888_Config: + index += 0; + break; + case SkBitmap::kRGB_565_Config: + index += 8; + break; + case SkBitmap::kARGB_4444_Config: + index += 16; + break; + case SkBitmap::kIndex8_Config: + index += 24; + break; + default: + return false; + } + + fRowProc = gProcs[index]; + fDstRow = (char*)dst->getPixels(); + fDstRowBytes = dst->rowBytes(); + fCurrY = 0; + return fRowProc != NULL; +} + +bool SkScaledBitmapSampler::next(const uint8_t* SK_RESTRICT src) { + SkASSERT((unsigned)fCurrY < (unsigned)fScaledHeight); + + bool hadAlpha = fRowProc(fDstRow, src + fX0 * fSrcPixelSize, fScaledWidth, + fDX * fSrcPixelSize, fCurrY); + fDstRow += fDstRowBytes; + fCurrY += 1; + return hadAlpha; +} diff --git a/src/images/SkScaledBitmapSampler.h b/src/images/SkScaledBitmapSampler.h new file mode 100644 index 0000000000..0bb99242a7 --- /dev/null +++ b/src/images/SkScaledBitmapSampler.h @@ -0,0 +1,55 @@ +#ifndef SkScaledBitmapSampler_DEFINED +#define SkScaledBitmapSampler_DEFINED + +#include "SkTypes.h" + +class SkBitmap; + +class SkScaledBitmapSampler { +public: + SkScaledBitmapSampler(int origWidth, int origHeight, int cellSize); + + int scaledWidth() const { return fScaledWidth; } + int scaledHeight() const { return fScaledHeight; } + + int srcY0() const { return fY0; } + int srcDY() const { return fDY; } + + enum SrcConfig { + kGray, // 1 byte per pixel + kIndex, // 1 byte per pixel + kRGB, // 3 bytes per pixel + kRGBX, // 4 byes per pixel (ignore 4th) + kRGBA // 4 bytes per pixel + }; + + // Given a dst bitmap (with pixels already allocated) and a src-config, + // prepares iterator to process the src colors and write them into dst. + // Returns false if the request cannot be fulfulled. + bool begin(SkBitmap* dst, SrcConfig sc, bool doDither); + // call with row of src pixels, for y = 0...scaledHeight-1. + // returns true if the row had non-opaque alpha in it + bool next(const uint8_t* SK_RESTRICT src); + +private: + int fScaledWidth; + int fScaledHeight; + + int fX0; // first X coord to sample + int fY0; // first Y coord (scanline) to sample + int fDX; // step between X samples + int fDY; // step between Y samples + + typedef bool (*RowProc)(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int y); + + // setup state + char* fDstRow; // points into bitmap's pixels + int fDstRowBytes; + int fCurrY; // used for dithering + int fSrcPixelSize; // 1, 3, 4 + RowProc fRowProc; +}; + +#endif diff --git a/src/images/SkStream.cpp b/src/images/SkStream.cpp new file mode 100644 index 0000000000..b199a1b944 --- /dev/null +++ b/src/images/SkStream.cpp @@ -0,0 +1,856 @@ +/* libs/graphics/images/SkStream.cpp +** +** Copyright 2006, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#include "SkStream.h" +#include "SkFixed.h" +#include "SkString.h" +#include "SkOSFile.h" + +SkStream::~SkStream() {} + +const char* SkStream::getFileName() +{ + // override in subclass if you represent a file + return NULL; +} + +const void* SkStream::getMemoryBase() +{ + // override in subclass if you represent a memory block + return NULL; +} + +size_t SkStream::skip(size_t size) +{ + /* Check for size == 0, and just return 0. If we passed that + to read(), it would interpret it as a request for the entire + size of the stream. + */ + return size ? this->read(NULL, size) : 0; +} + +int8_t SkStream::readS8() { + int8_t value; + size_t len = this->read(&value, 1); + SkASSERT(1 == len); + return value; +} + +int16_t SkStream::readS16() { + int16_t value; + size_t len = this->read(&value, 2); + SkASSERT(2 == len); + return value; +} + +int32_t SkStream::readS32() { + int32_t value; + size_t len = this->read(&value, 4); + SkASSERT(4 == len); + return value; +} + +SkScalar SkStream::readScalar() { + SkScalar value; + size_t len = this->read(&value, sizeof(SkScalar)); + SkASSERT(sizeof(SkScalar) == len); + return value; +} + +size_t SkStream::readPackedUInt() { + uint8_t byte; + if (!this->read(&byte, 1)) { + return 0; + } + if (byte != 0xFF) { + return byte; + } + + uint16_t word; + if (!this->read(&word, 2)) { + return 0; + } + if (word != 0xFFFF) { + return word; + } + + uint32_t quad; + if (!this->read(&quad, 4)) { + return 0; + } + return quad; +} + +////////////////////////////////////////////////////////////////////////////////////// + +SkWStream::~SkWStream() +{ +} + +void SkWStream::newline() +{ + this->write("\n", 1); +} + +void SkWStream::flush() +{ +} + +bool SkWStream::writeText(const char text[]) +{ + SkASSERT(text); + return this->write(text, strlen(text)); +} + +bool SkWStream::writeDecAsText(int32_t dec) +{ + SkString tmp; + tmp.appendS32(dec); + return this->write(tmp.c_str(), tmp.size()); +} + +bool SkWStream::writeHexAsText(uint32_t hex, int digits) +{ + SkString tmp; + tmp.appendHex(hex, digits); + return this->write(tmp.c_str(), tmp.size()); +} + +bool SkWStream::writeScalarAsText(SkScalar value) +{ + SkString tmp; + tmp.appendScalar(value); + return this->write(tmp.c_str(), tmp.size()); +} + +bool SkWStream::write8(U8CPU value) { + uint8_t v = SkToU8(value); + return this->write(&v, 1); +} + +bool SkWStream::write16(U16CPU value) { + uint16_t v = SkToU16(value); + return this->write(&v, 2); +} + +bool SkWStream::write32(uint32_t value) { + return this->write(&value, 4); +} + +bool SkWStream::writeScalar(SkScalar value) { + return this->write(&value, sizeof(value)); +} + +bool SkWStream::writePackedUInt(size_t value) { + if (value < 0xFF) { + return this->write8(value); + } else if (value < 0xFFFF) { + return this->write8(0xFF) && this->write16(value); + } else { + return this->write16(0xFFFF) && this->write32(value); + } +} + +bool SkWStream::writeStream(SkStream* stream, size_t length) { + char scratch[1024]; + const size_t MAX = sizeof(scratch); + + while (length != 0) { + size_t n = length; + if (n > MAX) { + n = MAX; + } + stream->read(scratch, n); + if (!this->write(scratch, n)) { + return false; + } + length -= n; + } + return true; +} + +//////////////////////////////////////////////////////////////////////////// + +SkFILEStream::SkFILEStream(const char file[]) : fName(file) +{ +#ifdef SK_BUILD_FOR_BREW + if (SkStrEndsWith(fName.c_str(), ".xml")) + fName.writable_str()[fName.size()-3] = 'b'; +#endif + + fFILE = file ? sk_fopen(fName.c_str(), kRead_SkFILE_Flag) : NULL; +} + +SkFILEStream::~SkFILEStream() +{ + if (fFILE) + sk_fclose(fFILE); +} + +void SkFILEStream::setPath(const char path[]) +{ + fName.set(path); +#ifdef SK_BUILD_FOR_BREW + if (SkStrEndsWith(fName.c_str(), ".xml")) + fName.writable_str()[fName.size()-3] = 'b'; +#endif + + if (fFILE) + { + sk_fclose(fFILE); + fFILE = NULL; + } + if (path) + fFILE = sk_fopen(fName.c_str(), kRead_SkFILE_Flag); +} + +const char* SkFILEStream::getFileName() +{ + return fName.c_str(); +} + +bool SkFILEStream::rewind() +{ + if (fFILE) + { + if (sk_frewind(fFILE)) + return true; + // we hit an error + sk_fclose(fFILE); + fFILE = NULL; + } + return false; +} + +size_t SkFILEStream::read(void* buffer, size_t size) +{ + if (fFILE) + { + if (buffer == NULL && size == 0) // special signature, they want the total size + return sk_fgetsize(fFILE); + else + return sk_fread(buffer, size, fFILE); + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////// + +SkMemoryStream::SkMemoryStream() +{ + fWeOwnTheData = false; + this->setMemory(NULL, 0); +} + +SkMemoryStream::SkMemoryStream(size_t size) { + fWeOwnTheData = true; + fOffset = 0; + fSize = size; + fSrc = sk_malloc_throw(size); +} + +SkMemoryStream::SkMemoryStream(const void* src, size_t size, bool copyData) +{ + fWeOwnTheData = false; + this->setMemory(src, size, copyData); +} + +SkMemoryStream::~SkMemoryStream() +{ + if (fWeOwnTheData) + sk_free((void*)fSrc); +} + +void SkMemoryStream::setMemory(const void* src, size_t size, bool copyData) +{ + if (fWeOwnTheData) + sk_free((void*)fSrc); + + fSize = size; + fOffset = 0; + fWeOwnTheData = copyData; + + if (copyData) + { + void* copy = sk_malloc_throw(size); + memcpy(copy, src, size); + src = copy; + } + fSrc = src; +} + +void SkMemoryStream::skipToAlign4() +{ + // cast to remove unary-minus warning + fOffset += -(int)fOffset & 0x03; +} + +bool SkMemoryStream::rewind() +{ + fOffset = 0; + return true; +} + +size_t SkMemoryStream::read(void* buffer, size_t size) +{ + if (buffer == NULL && size == 0) // special signature, they want the total size + return fSize; + + // if buffer is NULL, seek ahead by size + + if (size == 0) + return 0; + if (size > fSize - fOffset) + size = fSize - fOffset; + if (buffer) { + memcpy(buffer, (const char*)fSrc + fOffset, size); + } + fOffset += size; + return size; +} + +const void* SkMemoryStream::getMemoryBase() +{ + return fSrc; +} + +const void* SkMemoryStream::getAtPos() +{ + return (const char*)fSrc + fOffset; +} + +size_t SkMemoryStream::seek(size_t offset) +{ + if (offset > fSize) + offset = fSize; + fOffset = offset; + return offset; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +SkBufferStream::SkBufferStream(SkStream* proxy, size_t bufferSize) + : fProxy(proxy) +{ + SkASSERT(proxy != NULL); + proxy->ref(); + this->init(NULL, bufferSize); +} + +SkBufferStream::SkBufferStream(SkStream* proxy, void* buffer, size_t bufferSize) + : fProxy(proxy) +{ + SkASSERT(proxy != NULL); + SkASSERT(buffer == NULL || bufferSize != 0); // init(addr, 0) makes no sense, we must know how big their buffer is + proxy->ref(); + this->init(buffer, bufferSize); +} + +void SkBufferStream::init(void* buffer, size_t bufferSize) +{ + if (bufferSize == 0) + bufferSize = kDefaultBufferSize; + + fOrigBufferSize = bufferSize; + fBufferSize = bufferSize; + fBufferOffset = bufferSize; // to trigger a reload on the first read() + + if (buffer == NULL) + { + fBuffer = (char*)sk_malloc_throw(fBufferSize); + fWeOwnTheBuffer = true; + } + else + { + fBuffer = (char*)buffer; + fWeOwnTheBuffer = false; + } +} + +SkBufferStream::~SkBufferStream() +{ + fProxy->unref(); + if (fWeOwnTheBuffer) + sk_free(fBuffer); +} + +bool SkBufferStream::rewind() +{ + fBufferOffset = fBufferSize = fOrigBufferSize; + return fProxy->rewind(); +} + +const char* SkBufferStream::getFileName() +{ + return fProxy->getFileName(); +} + +#ifdef SK_DEBUG +// #define SK_TRACE_BUFFERSTREAM +#endif + +size_t SkBufferStream::read(void* buffer, size_t size) { +#ifdef SK_TRACE_BUFFERSTREAM + SkDebugf("Request %d", size); +#endif + + if (buffer == NULL && size == 0) { + return fProxy->read(buffer, size); // requesting total size + } + + if (0 == size) { + return 0; + } + + // skip size bytes + if (NULL == buffer) { + size_t remaining = fBufferSize - fBufferOffset; + if (remaining >= size) { + fBufferOffset += size; + return size; + } + // if we get here, we are being asked to skip beyond our current buffer + // so reset our offset to force a read next time, and skip the diff + // in our proxy + fBufferOffset = fOrigBufferSize; + return remaining + fProxy->read(NULL, size - remaining); + } + + size_t s = size; + size_t actuallyRead = 0; + + // flush what we can from our fBuffer + if (fBufferOffset < fBufferSize) + { + if (s > fBufferSize - fBufferOffset) + s = fBufferSize - fBufferOffset; + memcpy(buffer, fBuffer + fBufferOffset, s); +#ifdef SK_TRACE_BUFFERSTREAM + SkDebugf(" flush %d", s); +#endif + size -= s; + fBufferOffset += s; + buffer = (char*)buffer + s; + actuallyRead = s; + } + + // check if there is more to read + if (size) + { + SkASSERT(fBufferOffset >= fBufferSize); // need to refill our fBuffer + + if (size < fBufferSize) // lets try to read more than the request + { + s = fProxy->read(fBuffer, fBufferSize); +#ifdef SK_TRACE_BUFFERSTREAM + SkDebugf(" read %d into fBuffer", s); +#endif + if (size > s) // they asked for too much + size = s; + if (size) + { + memcpy(buffer, fBuffer, size); + actuallyRead += size; +#ifdef SK_TRACE_BUFFERSTREAM + SkDebugf(" memcpy %d into dst", size); +#endif + } + + fBufferOffset = size; + fBufferSize = s; // record the (possibly smaller) size for the buffer + } + else // just do a direct read + { + actuallyRead += fProxy->read(buffer, size); +#ifdef SK_TRACE_BUFFERSTREAM + SkDebugf(" direct read %d", size); +#endif + } + } +#ifdef SK_TRACE_BUFFERSTREAM + SkDebugf("\n"); +#endif + return actuallyRead; +} + +const void* SkBufferStream::getMemoryBase() +{ + return fProxy->getMemoryBase(); +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +SkFILEWStream::SkFILEWStream(const char path[]) +{ + fFILE = sk_fopen(path, kWrite_SkFILE_Flag); +} + +SkFILEWStream::~SkFILEWStream() +{ + if (fFILE) + sk_fclose(fFILE); +} + +bool SkFILEWStream::write(const void* buffer, size_t size) +{ + if (fFILE == NULL) + return false; + + if (sk_fwrite(buffer, size, fFILE) != size) + { + SkDEBUGCODE(SkDebugf("SkFILEWStream failed writing %d bytes\n", size);) + sk_fclose(fFILE); + fFILE = NULL; + return false; + } + return true; +} + +void SkFILEWStream::flush() +{ + if (fFILE) + sk_fflush(fFILE); +} + +//////////////////////////////////////////////////////////////////////// + +SkMemoryWStream::SkMemoryWStream(void* buffer, size_t size) + : fBuffer((char*)buffer), fMaxLength(size), fBytesWritten(0) +{ +} + +bool SkMemoryWStream::write(const void* buffer, size_t size) +{ + size = SkMin32(size, fMaxLength - fBytesWritten); + if (size > 0) + { + memcpy(fBuffer + fBytesWritten, buffer, size); + fBytesWritten += size; + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////////// + +#define SkDynamicMemoryWStream_MinBlockSize 256 + +struct SkDynamicMemoryWStream::Block { + Block* fNext; + char* fCurr; + char* fStop; + + const char* start() const { return (const char*)(this + 1); } + char* start() { return (char*)(this + 1); } + size_t avail() const { return fStop - fCurr; } + size_t written() const { return fCurr - this->start(); } + + void init(size_t size) + { + fNext = NULL; + fCurr = this->start(); + fStop = this->start() + size; + } + + const void* append(const void* data, size_t size) + { + SkASSERT((size_t)(fStop - fCurr) >= size); + memcpy(fCurr, data, size); + fCurr += size; + return (const void*)((const char*)data + size); + } +}; + +SkDynamicMemoryWStream::SkDynamicMemoryWStream() : fHead(NULL), fTail(NULL), fBytesWritten(0), fCopyToCache(NULL) +{ +} + +SkDynamicMemoryWStream::~SkDynamicMemoryWStream() +{ + reset(); +} + +const char* SkDynamicMemoryWStream::detach() +{ + const char* result = getStream(); + fCopyToCache = NULL; + return result; +} + +void SkDynamicMemoryWStream::reset() +{ + sk_free(fCopyToCache); + Block* block = fHead; + + while (block != NULL) { + Block* next = block->fNext; + sk_free(block); + block = next; + } + fHead = fTail = NULL; + fBytesWritten = 0; + fCopyToCache = NULL; +} + +bool SkDynamicMemoryWStream::write(const void* buffer, size_t count) +{ + if (count > 0) { + + if (fCopyToCache) { + sk_free(fCopyToCache); + fCopyToCache = NULL; + } + fBytesWritten += count; + + size_t size; + + if (fTail != NULL && fTail->avail() > 0) { + size = SkMin32(fTail->avail(), count); + buffer = fTail->append(buffer, size); + SkASSERT(count >= size); + count -= size; + if (count == 0) + return true; + } + + size = SkMax32(count, SkDynamicMemoryWStream_MinBlockSize); + Block* block = (Block*)sk_malloc_throw(sizeof(Block) + size); + block->init(size); + block->append(buffer, count); + + if (fTail != NULL) + fTail->fNext = block; + else + fHead = fTail = block; + fTail = block; + } + return true; +} + +bool SkDynamicMemoryWStream::write(const void* buffer, size_t offset, size_t count) +{ + if (offset + count > fBytesWritten) + return false; // test does not partially modify + Block* block = fHead; + while (block != NULL) { + size_t size = block->written(); + if (offset < size) { + size_t part = offset + count > size ? size - offset : count; + memcpy(block->start() + offset, buffer, part); + if (count <= part) + return true; + count -= part; + buffer = (const void*) ((char* ) buffer + part); + } + offset = offset > size ? offset - size : 0; + block = block->fNext; + } + return false; +} + +bool SkDynamicMemoryWStream::read(void* buffer, size_t offset, size_t count) +{ + if (offset + count > fBytesWritten) + return false; // test does not partially modify + Block* block = fHead; + while (block != NULL) { + size_t size = block->written(); + if (offset < size) { + size_t part = offset + count > size ? size - offset : count; + memcpy(buffer, block->start() + offset, part); + if (count <= part) + return true; + count -= part; + buffer = (void*) ((char* ) buffer + part); + } + offset = offset > size ? offset - size : 0; + block = block->fNext; + } + return false; +} + +void SkDynamicMemoryWStream::copyTo(void* dst) const +{ + Block* block = fHead; + + while (block != NULL) { + size_t size = block->written(); + memcpy(dst, block->start(), size); + dst = (void*)((char*)dst + size); + block = block->fNext; + } +} + +const char* SkDynamicMemoryWStream::getStream() const +{ + if (fCopyToCache == NULL) { + fCopyToCache = (char*)sk_malloc_throw(fBytesWritten); + this->copyTo(fCopyToCache); + } + return fCopyToCache; +} + +void SkDynamicMemoryWStream::padToAlign4() +{ + // cast to remove unary-minus warning + int padBytes = -(int)fBytesWritten & 0x03; + if (padBytes == 0) + return; + int zero = 0; + write(&zero, padBytes); +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkDebugWStream::newline() +{ +#ifdef SK_DEBUG + SkDebugf("\n"); +#endif +} + +bool SkDebugWStream::write(const void* buffer, size_t size) +{ +#ifdef SK_DEBUG + char* s = new char[size+1]; + memcpy(s, buffer, size); + s[size] = 0; + SkDebugf("%s", s); + delete[] s; +#endif + return true; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef SK_DEBUG + +#include "SkRandom.h" + +#ifdef SK_SUPPORT_UNITTEST +#define MAX_SIZE (256 * 1024) + +static void random_fill(SkRandom& rand, void* buffer, size_t size) { + char* p = (char*)buffer; + char* stop = p + size; + while (p < stop) { + *p++ = (char)(rand.nextU() >> 8); + } +} + +static void test_buffer() { + SkRandom rand; + SkAutoMalloc am(MAX_SIZE * 2); + char* storage = (char*)am.get(); + char* storage2 = storage + MAX_SIZE; + + random_fill(rand, storage, MAX_SIZE); + + for (int sizeTimes = 0; sizeTimes < 100; sizeTimes++) { + int size = rand.nextU() % MAX_SIZE; + if (size == 0) { + size = MAX_SIZE; + } + for (int times = 0; times < 100; times++) { + int bufferSize = 1 + (rand.nextU() & 0xFFFF); + SkMemoryStream mstream(storage, size); + SkBufferStream bstream(&mstream, bufferSize); + + int bytesRead = 0; + while (bytesRead < size) { + int s = 17 + (rand.nextU() & 0xFFFF); + int ss = bstream.read(storage2, s); + SkASSERT(ss > 0 && ss <= s); + SkASSERT(bytesRead + ss <= size); + SkASSERT(memcmp(storage + bytesRead, storage2, ss) == 0); + bytesRead += ss; + } + SkASSERT(bytesRead == size); + } + } +} +#endif + +void SkStream::UnitTest() { +#ifdef SK_SUPPORT_UNITTEST + { + static const char s[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + char copy[sizeof(s)]; + SkRandom rand; + + for (int i = 0; i < 65; i++) + { + char* copyPtr = copy; + SkMemoryStream mem(s, sizeof(s)); + SkBufferStream buff(&mem, i); + + do { + copyPtr += buff.read(copyPtr, rand.nextU() & 15); + } while (copyPtr < copy + sizeof(s)); + SkASSERT(copyPtr == copy + sizeof(s)); + SkASSERT(memcmp(s, copy, sizeof(s)) == 0); + } + } + test_buffer(); +#endif +} + +void SkWStream::UnitTest() +{ +#ifdef SK_SUPPORT_UNITTEST + { + SkDebugWStream s; + + s.writeText("testing wstream helpers\n"); + s.writeText("compare: 0 "); s.writeDecAsText(0); s.newline(); + s.writeText("compare: 591 "); s.writeDecAsText(591); s.newline(); + s.writeText("compare: -9125 "); s.writeDecAsText(-9125); s.newline(); + s.writeText("compare: 0 "); s.writeHexAsText(0, 0); s.newline(); + s.writeText("compare: 03FA "); s.writeHexAsText(0x3FA, 4); s.newline(); + s.writeText("compare: DEADBEEF "); s.writeHexAsText(0xDEADBEEF, 4); s.newline(); + s.writeText("compare: 0 "); s.writeScalarAsText(SkIntToScalar(0)); s.newline(); + s.writeText("compare: 27 "); s.writeScalarAsText(SkIntToScalar(27)); s.newline(); + s.writeText("compare: -119 "); s.writeScalarAsText(SkIntToScalar(-119)); s.newline(); + s.writeText("compare: 851.3333 "); s.writeScalarAsText(SkIntToScalar(851) + SK_Scalar1/3); s.newline(); + s.writeText("compare: -0.08 "); s.writeScalarAsText(-SK_Scalar1*8/100); s.newline(); + } + + { + SkDynamicMemoryWStream ds; + const char s[] = "abcdefghijklmnopqrstuvwxyz"; + int i; + for (i = 0; i < 100; i++) { + bool result = ds.write(s, 26); + SkASSERT(result); + } + SkASSERT(ds.getOffset() == 100 * 26); + char* dst = new char[100 * 26 + 1]; + dst[100*26] = '*'; + ds.copyTo(dst); + SkASSERT(dst[100*26] == '*'); + // char* p = dst; + for (i = 0; i < 100; i++) + SkASSERT(memcmp(&dst[i * 26], s, 26) == 0); + SkASSERT(memcmp(dst, ds.getStream(), 100*26) == 0); + delete[] dst; + } +#endif +} + +#endif diff --git a/src/images/bmpdecoderhelper.cpp b/src/images/bmpdecoderhelper.cpp new file mode 100644 index 0000000000..acabf44156 --- /dev/null +++ b/src/images/bmpdecoderhelper.cpp @@ -0,0 +1,376 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Author: cevans@google.com (Chris Evans) + +#include "bmpdecoderhelper.h" + +namespace image_codec { + +static const int kBmpHeaderSize = 14; +static const int kBmpInfoSize = 40; +static const int kBmpOS2InfoSize = 12; +static const int kMaxDim = SHRT_MAX / 2; + +bool BmpDecoderHelper::DecodeImage(const char* p, + int len, + int max_pixels, + BmpDecoderCallback* callback) { + data_ = reinterpret_cast<const uint8*>(p); + pos_ = 0; + len_ = len; + inverted_ = true; + // Parse the header structure. + if (len < kBmpHeaderSize + 4) { + return false; + } + GetShort(); // Signature. + GetInt(); // Size. + GetInt(); // Reserved. + int offset = GetInt(); + // Parse the info structure. + int infoSize = GetInt(); + if (infoSize != kBmpOS2InfoSize && infoSize < kBmpInfoSize) { + return false; + } + int cols = 0; + int comp = 0; + int colLen = 4; + if (infoSize >= kBmpInfoSize) { + if (len < kBmpHeaderSize + kBmpInfoSize) { + return false; + } + width_ = GetInt(); + height_ = GetInt(); + GetShort(); // Planes. + bpp_ = GetShort(); + comp = GetInt(); + GetInt(); // Size. + GetInt(); // XPPM. + GetInt(); // YPPM. + cols = GetInt(); + GetInt(); // Important colours. + } else { + if (len < kBmpHeaderSize + kBmpOS2InfoSize) { + return false; + } + colLen = 3; + width_ = GetShort(); + height_ = GetShort(); + GetShort(); // Planes. + bpp_ = GetShort(); + } + if (height_ < 0) { + height_ = -height_; + inverted_ = false; + } + if (width_ <= 0 || width_ > kMaxDim || height_ <= 0 || height_ > kMaxDim) { + return false; + } + if (width_ * height_ > max_pixels) { + return false; + } + if (cols < 0 || cols > 256) { + return false; + } + // Allocate then read in the colour map. + if (cols == 0 && bpp_ <= 8) { + cols = 1 << bpp_; + } + if (bpp_ <= 8 || cols > 0) { + uint8* colBuf = new uint8[256 * 3]; + memset(colBuf, '\0', 256 * 3); + colTab_.reset(colBuf); + } + if (cols > 0) { + if (pos_ + (cols * colLen) > len_) { + return false; + } + for (int i = 0; i < cols; ++i) { + int base = i * 3; + colTab_[base + 2] = GetByte(); + colTab_[base + 1] = GetByte(); + colTab_[base] = GetByte(); + if (colLen == 4) { + GetByte(); + } + } + } + // Read in the compression data if necessary. + redBits_ = 0x7c00; + greenBits_ = 0x03e0; + blueBits_ = 0x001f; + bool rle = false; + if (comp == 1 || comp == 2) { + rle = true; + } else if (comp == 3) { + if (pos_ + 12 > len_) { + return false; + } + redBits_ = GetInt() & 0xffff; + greenBits_ = GetInt() & 0xffff; + blueBits_ = GetInt() & 0xffff; + } + redShiftRight_ = CalcShiftRight(redBits_); + greenShiftRight_ = CalcShiftRight(greenBits_); + blueShiftRight_ = CalcShiftRight(blueBits_); + redShiftLeft_ = CalcShiftLeft(redBits_); + greenShiftLeft_ = CalcShiftLeft(greenBits_); + blueShiftLeft_ = CalcShiftLeft(blueBits_); + rowPad_ = 0; + pixelPad_ = 0; + int rowLen; + if (bpp_ == 32) { + rowLen = width_ * 4; + pixelPad_ = 1; + } else if (bpp_ == 24) { + rowLen = width_ * 3; + } else if (bpp_ == 16) { + rowLen = width_ * 2; + } else if (bpp_ == 8) { + rowLen = width_; + } else if (bpp_ == 4) { + rowLen = width_ / 2; + if (width_ & 1) { + rowLen++; + } + } else if (bpp_ == 1) { + rowLen = width_ / 8; + if (width_ & 7) { + rowLen++; + } + } else { + return false; + } + // Round the rowLen up to a multiple of 4. + if (rowLen % 4 != 0) { + rowPad_ = 4 - (rowLen % 4); + rowLen += rowPad_; + } + + if (offset > 0 && offset > pos_ && offset < len_) { + pos_ = offset; + } + // Deliberately off-by-one; a load of BMPs seem to have their last byte + // missing. + if (!rle && (pos_ + (rowLen * height_) > len_ + 1)) { + return false; + } + + output_ = callback->SetSize(width_, height_); + if (NULL == output_) { + return true; // meaning we succeeded, but they want us to stop now + } + + if (rle && (bpp_ == 4 || bpp_ == 8)) { + DoRLEDecode(); + } else { + DoStandardDecode(); + } + return true; +} + +void BmpDecoderHelper::DoRLEDecode() { + static const uint8 RLE_ESCAPE = 0; + static const uint8 RLE_EOL = 0; + static const uint8 RLE_EOF = 1; + static const uint8 RLE_DELTA = 2; + int x = 0; + int y = height_ - 1; + while (pos_ < len_ - 1) { + uint8 cmd = GetByte(); + if (cmd != RLE_ESCAPE) { + uint8 pixels = GetByte(); + int num = 0; + uint8 col = pixels; + while (cmd-- && x < width_) { + if (bpp_ == 4) { + if (num & 1) { + col = pixels & 0xf; + } else { + col = pixels >> 4; + } + } + PutPixel(x++, y, col); + num++; + } + } else { + cmd = GetByte(); + if (cmd == RLE_EOF) { + return; + } else if (cmd == RLE_EOL) { + x = 0; + y--; + if (y < 0) { + return; + } + } else if (cmd == RLE_DELTA) { + if (pos_ < len_ - 1) { + uint8 dx = GetByte(); + uint8 dy = GetByte(); + x += dx; + if (x > width_) { + x = width_; + } + y -= dy; + if (y < 0) { + return; + } + } + } else { + int num = 0; + int bytesRead = 0; + uint8 val = 0; + while (cmd-- && pos_ < len_) { + if (bpp_ == 8 || !(num & 1)) { + val = GetByte(); + bytesRead++; + } + uint8 col = val; + if (bpp_ == 4) { + if (num & 1) { + col = col & 0xf; + } else { + col >>= 4; + } + } + if (x < width_) { + PutPixel(x++, y, col); + } + num++; + } + // All pixel runs must be an even number of bytes - skip a byte if we + // read an odd number. + if ((bytesRead & 1) && pos_ < len_) { + GetByte(); + } + } + } + } +} + +void BmpDecoderHelper::PutPixel(int x, int y, uint8 col) { + CHECK(x >= 0 && x < width_); + CHECK(y >= 0 && y < height_); + if (!inverted_) { + y = height_ - (y + 1); + } + + int base = ((y * width_) + x) * 3; + int colBase = col * 3; + output_[base] = colTab_[colBase]; + output_[base + 1] = colTab_[colBase + 1]; + output_[base + 2] = colTab_[colBase + 2]; +} + +void BmpDecoderHelper::DoStandardDecode() { + int row = 0; + uint8 currVal = 0; + for (int h = height_ - 1; h >= 0; h--, row++) { + int realH = h; + if (!inverted_) { + realH = height_ - (h + 1); + } + uint8* line = output_ + (3 * width_ * realH); + for (int w = 0; w < width_; w++) { + if (bpp_ >= 24) { + line[2] = GetByte(); + line[1] = GetByte(); + line[0] = GetByte(); + } else if (bpp_ == 16) { + uint32 val = GetShort(); + line[0] = ((val & redBits_) >> redShiftRight_) << redShiftLeft_; + line[1] = ((val & greenBits_) >> greenShiftRight_) << greenShiftLeft_; + line[2] = ((val & blueBits_) >> blueShiftRight_) << blueShiftLeft_; + } else if (bpp_ <= 8) { + uint8 col; + if (bpp_ == 8) { + col = GetByte(); + } else if (bpp_ == 4) { + if ((w % 2) == 0) { + currVal = GetByte(); + col = currVal >> 4; + } else { + col = currVal & 0xf; + } + } else { + if ((w % 8) == 0) { + currVal = GetByte(); + } + int bit = w & 7; + col = ((currVal >> (7 - bit)) & 1); + } + int base = col * 3; + line[0] = colTab_[base]; + line[1] = colTab_[base + 1]; + line[2] = colTab_[base + 2]; + } + line += 3; + for (int i = 0; i < pixelPad_; ++i) { + GetByte(); + } + } + for (int i = 0; i < rowPad_; ++i) { + GetByte(); + } + } +} + +int BmpDecoderHelper::GetInt() { + uint8 b1 = GetByte(); + uint8 b2 = GetByte(); + uint8 b3 = GetByte(); + uint8 b4 = GetByte(); + return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24); +} + +int BmpDecoderHelper::GetShort() { + uint8 b1 = GetByte(); + uint8 b2 = GetByte(); + return b1 | (b2 << 8); +} + +uint8 BmpDecoderHelper::GetByte() { + CHECK(pos_ >= 0 && pos_ <= len_); + // We deliberately allow this off-by-one access to cater for BMPs with their + // last byte missing. + if (pos_ == len_) { + return 0; + } + return data_[pos_++]; +} + +int BmpDecoderHelper::CalcShiftRight(uint32 mask) { + int ret = 0; + while (mask != 0 && !(mask & 1)) { + mask >>= 1; + ret++; + } + return ret; +} + +int BmpDecoderHelper::CalcShiftLeft(uint32 mask) { + int ret = 0; + while (mask != 0 && !(mask & 1)) { + mask >>= 1; + } + while (mask != 0 && !(mask & 0x80)) { + mask <<= 1; + ret++; + } + return ret; +} + +} // namespace image_codec diff --git a/src/images/bmpdecoderhelper.h b/src/images/bmpdecoderhelper.h new file mode 100644 index 0000000000..07f0ae5646 --- /dev/null +++ b/src/images/bmpdecoderhelper.h @@ -0,0 +1,123 @@ +/* + * Copyright 2007, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMAGE_CODEC_BMPDECODERHELPER_H__ +#define IMAGE_CODEC_BMPDECODERHELPER_H__ + +/////////////////////////////////////////////////////////////////////////////// +// this section is my current "glue" between google3 code and android. +// will be fixed soon + +#include "SkTypes.h" +#include <limits.h> +#define DISALLOW_EVIL_CONSTRUCTORS(name) +#define CHECK(predicate) SkASSERT(predicate) +typedef uint8_t uint8; +typedef uint32_t uint32; + +template <typename T> class scoped_array { +private: + T* ptr_; + scoped_array(scoped_array const&); + scoped_array& operator=(const scoped_array&); + +public: + explicit scoped_array(T* p = 0) : ptr_(p) {} + ~scoped_array() { + delete[] ptr_; + } + + void reset(T* p = 0) { + if (p != ptr_) { + delete[] ptr_; + ptr_ = p; + } + } + + T& operator[](int i) const { + return ptr_[i]; + } +}; + +/////////////////////////////////////////////////////////////////////////////// + +namespace image_codec { + +class BmpDecoderCallback { + public: + BmpDecoderCallback() { } + virtual ~BmpDecoderCallback() {} + + /** + * This is called once for an image. It is passed the width and height and + * should return the address of a buffer that is large enough to store + * all of the resulting pixels (widht * height * 3 bytes). If it returns NULL, + * then the decoder will abort, but return true, as the caller has received + * valid dimensions. + */ + virtual uint8* SetSize(int width, int height) = 0; + + private: + DISALLOW_EVIL_CONSTRUCTORS(BmpDecoderCallback); +}; + +class BmpDecoderHelper { + public: + BmpDecoderHelper() { } + ~BmpDecoderHelper() { } + bool DecodeImage(const char* data, + int len, + int max_pixels, + BmpDecoderCallback* callback); + + private: + DISALLOW_EVIL_CONSTRUCTORS(BmpDecoderHelper); + + void DoRLEDecode(); + void DoStandardDecode(); + void PutPixel(int x, int y, uint8 col); + + int GetInt(); + int GetShort(); + uint8 GetByte(); + int CalcShiftRight(uint32 mask); + int CalcShiftLeft(uint32 mask); + + const uint8* data_; + int pos_; + int len_; + int width_; + int height_; + int bpp_; + int pixelPad_; + int rowPad_; + scoped_array<uint8> colTab_; + uint32 redBits_; + uint32 greenBits_; + uint32 blueBits_; + int redShiftRight_; + int greenShiftRight_; + int blueShiftRight_; + int redShiftLeft_; + int greenShiftLeft_; + int blueShiftLeft_; + uint8* output_; + bool inverted_; +}; + +} // namespace + +#endif diff --git a/src/images/fpdfemb_ext.h b/src/images/fpdfemb_ext.h new file mode 100644 index 0000000000..d82c4df37c --- /dev/null +++ b/src/images/fpdfemb_ext.h @@ -0,0 +1,81 @@ +#ifdef __cplusplus +extern "C" { +#endif + +/** Extended interfaces for JPEG, JPEG2000 and JBIG2 decoders **/ +typedef struct +{ + /** Initialize the decoding context, with memory allocator provided by FPDFEMB. + Implementation should return a pointer to the decoding context. + */ + void* (*Init)(void* (*alloc_func)(unsigned int), void (*free_func)(void*)); + + /** Finish with the decoding. */ + void (*Finish)(void* pContext); + + /** Input JPEG encoded data from the source. + This function may be called multiple times during decoding progress. + */ + void (*Input)(void* pContext, const unsigned char* src_buf, unsigned long src_size); + + /** Read the header information. Return non-zero for success, 0 for failure */ + int (*ReadHeader)(void* pContext); + + /** Get info from the decoder, including image width, height and number of components */ + void (*GetInfo)(void* pContext, int* width, int* height, int* nComps); + + /** Read one scanline from decoded image */ + int (*ReadScanline)(void* pContext, unsigned char* dest_buf); + + /** Get number of available source bytes left in the input stream */ + unsigned long (*GetAvailInput)(void* pContext); +} FPDFEMB_JPEG_DECODER; + +void FPDFEMB_SetJpegDecoder(FPDFEMB_JPEG_DECODER* pDecoder); + +typedef struct +{ + /** Initialize the decoder with the full source data. + Implementation should return a pointer to the context. + */ + void* (*Init)(const unsigned char* src_buf, unsigned long src_size); + + /** Destroy the context */ + void (*Finish)(void* context); + + /** Get image info from the context, including width, height, number of components + in original codestream, and number of components in output image. For some + particular type of encoded image, like paletted image, these two numbers of + components may vary. + */ + void (*GetInfo)(void* context, unsigned long* width, unsigned long* height, + unsigned long* codestream_nComps, unsigned long* output_nComps); + + /** Do the real data decoding, output to a pre-allocated buffer. + bTranslateColor indicates whether the decoder should use JPEG2000 embedded + color space info to translate image into sRGB color space. + "offsets" array describes the byte order of all components. For example, + {2,1,0} means the first components is output to last byte. + */ + void (*Decode)(void* context, unsigned char* dest_buf, int pitch, + int bTranslateColor, unsigned char* offsets); +} FPDFEMB_JPEG2000_DECODER; + +void FPDFEMB_SetJpeg2000Decoder(FPDFEMB_JPEG2000_DECODER* pDecoder); + +typedef struct +{ + /** Do the whole decoding process. Supplied parameters include width, height, source image + data and size, global data and size (can be shared among different images), destination + buffer and scanline pitch in dest buffer. + */ + void (*Decode)(unsigned long width, unsigned long height, const unsigned char* src_buf, + unsigned long src_size, const unsigned char* global_buf, unsigned long global_size, + unsigned char* dest_buf, int dest_pitch); +} FPDFEMB_JBIG2_DECODER; + +void FPDFEMB_SetJbig2Decoder(FPDFEMB_JBIG2_DECODER* pDecoder); + +#ifdef __cplusplus +}; +#endif |