Derek Sollenberger 83ccff716f Move android.graphics JNI & APEX files into HWUI
The graphics JNI code is now separate from libandroid_runtime
and it along with HWUI headers are no longer visible to targets
outside the boundary of what is to become the UI mainline module

The exposed headers to targets outside the module are now restriced
to C APIs contained in the apex header directory.

Bug: 137655431
Test: CtsUiRenderingTestCases
Change-Id: I30d34055b6870dc1039f190a88f4a747cee17300
2020-02-19 21:13:34 -05:00

448 lines
13 KiB
C++

/*
* Copyright 2006 The Android Open Source Project
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "Movie.h"
#include "SkColor.h"
#include "SkColorPriv.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkUtils.h"
#include "gif_lib.h"
#if GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0)
#define DGifCloseFile(a, b) DGifCloseFile(a)
#endif
class GIFMovie : public Movie {
public:
explicit GIFMovie(SkStream* stream);
virtual ~GIFMovie();
protected:
virtual bool onGetInfo(Info*);
virtual bool onSetTime(SkMSec);
virtual bool onGetBitmap(SkBitmap*);
private:
GifFileType* fGIF;
int fCurrIndex;
int fLastDrawIndex;
SkBitmap fBackup;
SkColor fPaintingColor;
};
static int Decode(GifFileType* fileType, GifByteType* out, int size) {
SkStream* stream = (SkStream*) fileType->UserData;
return (int) stream->read(out, size);
}
GIFMovie::GIFMovie(SkStream* stream)
{
#if GIFLIB_MAJOR < 5
fGIF = DGifOpen( stream, Decode );
#else
fGIF = DGifOpen( stream, Decode, nullptr );
#endif
if (nullptr == fGIF)
return;
if (DGifSlurp(fGIF) != GIF_OK)
{
DGifCloseFile(fGIF, nullptr);
fGIF = nullptr;
}
fCurrIndex = -1;
fLastDrawIndex = -1;
fPaintingColor = SkPackARGB32(0, 0, 0, 0);
}
GIFMovie::~GIFMovie()
{
if (fGIF)
DGifCloseFile(fGIF, nullptr);
}
static SkMSec savedimage_duration(const SavedImage* image)
{
for (int j = 0; j < image->ExtensionBlockCount; j++)
{
if (image->ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE)
{
SkASSERT(image->ExtensionBlocks[j].ByteCount >= 4);
const uint8_t* b = (const uint8_t*)image->ExtensionBlocks[j].Bytes;
return ((b[2] << 8) | b[1]) * 10;
}
}
return 0;
}
bool GIFMovie::onGetInfo(Info* info)
{
if (nullptr == 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 GIFMovie::onSetTime(SkMSec time)
{
if (nullptr == fGIF)
return false;
SkMSec dur = 0;
for (int i = 0; i < fGIF->ImageCount; i++)
{
dur += savedimage_duration(&fGIF->SavedImages[i]);
if (dur >= time)
{
fCurrIndex = i;
return fLastDrawIndex != fCurrIndex;
}
}
fCurrIndex = fGIF->ImageCount - 1;
return true;
}
static void copyLine(uint32_t* dst, const unsigned char* src, const ColorMapObject* cmap,
int transparent, int width)
{
for (; width > 0; width--, src++, dst++) {
if (*src != transparent && *src < cmap->ColorCount) {
const GifColorType& col = cmap->Colors[*src];
*dst = SkPackARGB32(0xFF, col.Red, col.Green, col.Blue);
}
}
}
#if GIFLIB_MAJOR < 5
static void copyInterlaceGroup(SkBitmap* bm, const unsigned char*& src,
const ColorMapObject* cmap, int transparent, int copyWidth,
int copyHeight, const GifImageDesc& imageDesc, int rowStep,
int startRow)
{
int row;
// every 'rowStep'th row, starting with row 'startRow'
for (row = startRow; row < copyHeight; row += rowStep) {
uint32_t* dst = bm->getAddr32(imageDesc.Left, imageDesc.Top + row);
copyLine(dst, src, cmap, transparent, copyWidth);
src += imageDesc.Width;
}
// pad for rest height
src += imageDesc.Width * ((imageDesc.Height - row + rowStep - 1) / rowStep);
}
static void blitInterlace(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap,
int transparent)
{
int width = bm->width();
int height = bm->height();
GifWord copyWidth = frame->ImageDesc.Width;
if (frame->ImageDesc.Left + copyWidth > width) {
copyWidth = width - frame->ImageDesc.Left;
}
GifWord copyHeight = frame->ImageDesc.Height;
if (frame->ImageDesc.Top + copyHeight > height) {
copyHeight = height - frame->ImageDesc.Top;
}
// deinterlace
const unsigned char* src = (unsigned char*)frame->RasterBits;
// group 1 - every 8th row, starting with row 0
copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 8, 0);
// group 2 - every 8th row, starting with row 4
copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 8, 4);
// group 3 - every 4th row, starting with row 2
copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 4, 2);
copyInterlaceGroup(bm, src, cmap, transparent, copyWidth, copyHeight, frame->ImageDesc, 2, 1);
}
#endif
static void blitNormal(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap,
int transparent)
{
int width = bm->width();
int height = bm->height();
const unsigned char* src = (unsigned char*)frame->RasterBits;
uint32_t* dst = bm->getAddr32(frame->ImageDesc.Left, frame->ImageDesc.Top);
GifWord copyWidth = frame->ImageDesc.Width;
if (frame->ImageDesc.Left + copyWidth > width) {
copyWidth = width - frame->ImageDesc.Left;
}
GifWord copyHeight = frame->ImageDesc.Height;
if (frame->ImageDesc.Top + copyHeight > height) {
copyHeight = height - frame->ImageDesc.Top;
}
for (; copyHeight > 0; copyHeight--) {
copyLine(dst, src, cmap, transparent, copyWidth);
src += frame->ImageDesc.Width;
dst += width;
}
}
static void fillRect(SkBitmap* bm, GifWord left, GifWord top, GifWord width, GifWord height,
uint32_t col)
{
int bmWidth = bm->width();
int bmHeight = bm->height();
uint32_t* dst = bm->getAddr32(left, top);
GifWord copyWidth = width;
if (left + copyWidth > bmWidth) {
copyWidth = bmWidth - left;
}
GifWord copyHeight = height;
if (top + copyHeight > bmHeight) {
copyHeight = bmHeight - top;
}
for (; copyHeight > 0; copyHeight--) {
sk_memset32(dst, col, copyWidth);
dst += bmWidth;
}
}
static void drawFrame(SkBitmap* bm, const SavedImage* frame, const ColorMapObject* cmap)
{
int transparent = -1;
for (int i = 0; i < frame->ExtensionBlockCount; ++i) {
ExtensionBlock* eb = frame->ExtensionBlocks + i;
if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
eb->ByteCount == 4) {
bool has_transparency = ((eb->Bytes[0] & 1) == 1);
if (has_transparency) {
transparent = (unsigned char)eb->Bytes[3];
}
}
}
if (frame->ImageDesc.ColorMap != nullptr) {
// use local color table
cmap = frame->ImageDesc.ColorMap;
}
if (cmap == nullptr || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
SkDEBUGFAIL("bad colortable setup");
return;
}
#if GIFLIB_MAJOR < 5
// before GIFLIB 5, de-interlacing wasn't done by library at load time
if (frame->ImageDesc.Interlace) {
blitInterlace(bm, frame, cmap, transparent);
return;
}
#endif
blitNormal(bm, frame, cmap, transparent);
}
static bool checkIfWillBeCleared(const SavedImage* frame)
{
for (int i = 0; i < frame->ExtensionBlockCount; ++i) {
ExtensionBlock* eb = frame->ExtensionBlocks + i;
if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
eb->ByteCount == 4) {
// check disposal method
int disposal = ((eb->Bytes[0] >> 2) & 7);
if (disposal == 2 || disposal == 3) {
return true;
}
}
}
return false;
}
static void getTransparencyAndDisposalMethod(const SavedImage* frame, bool* trans, int* disposal)
{
*trans = false;
*disposal = 0;
for (int i = 0; i < frame->ExtensionBlockCount; ++i) {
ExtensionBlock* eb = frame->ExtensionBlocks + i;
if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
eb->ByteCount == 4) {
*trans = ((eb->Bytes[0] & 1) == 1);
*disposal = ((eb->Bytes[0] >> 2) & 7);
}
}
}
// return true if area of 'target' is completely covers area of 'covered'
static bool checkIfCover(const SavedImage* target, const SavedImage* covered)
{
if (target->ImageDesc.Left <= covered->ImageDesc.Left
&& covered->ImageDesc.Left + covered->ImageDesc.Width <=
target->ImageDesc.Left + target->ImageDesc.Width
&& target->ImageDesc.Top <= covered->ImageDesc.Top
&& covered->ImageDesc.Top + covered->ImageDesc.Height <=
target->ImageDesc.Top + target->ImageDesc.Height) {
return true;
}
return false;
}
static void disposeFrameIfNeeded(SkBitmap* bm, const SavedImage* cur, const SavedImage* next,
SkBitmap* backup, SkColor color)
{
// We can skip disposal process if next frame is not transparent
// and completely covers current area
bool curTrans;
int curDisposal;
getTransparencyAndDisposalMethod(cur, &curTrans, &curDisposal);
bool nextTrans;
int nextDisposal;
getTransparencyAndDisposalMethod(next, &nextTrans, &nextDisposal);
if ((curDisposal == 2 || curDisposal == 3)
&& (nextTrans || !checkIfCover(next, cur))) {
switch (curDisposal) {
// restore to background color
// -> 'background' means background under this image.
case 2:
fillRect(bm, cur->ImageDesc.Left, cur->ImageDesc.Top,
cur->ImageDesc.Width, cur->ImageDesc.Height,
color);
break;
// restore to previous
case 3:
bm->swap(*backup);
break;
}
}
// Save current image if next frame's disposal method == 3
if (nextDisposal == 3) {
const uint32_t* src = bm->getAddr32(0, 0);
uint32_t* dst = backup->getAddr32(0, 0);
int cnt = bm->width() * bm->height();
memcpy(dst, src, cnt*sizeof(uint32_t));
}
}
bool GIFMovie::onGetBitmap(SkBitmap* bm)
{
const GifFileType* gif = fGIF;
if (nullptr == gif)
return false;
if (gif->ImageCount < 1) {
return false;
}
const int width = gif->SWidth;
const int height = gif->SHeight;
if (width <= 0 || height <= 0) {
return false;
}
// no need to draw
if (fLastDrawIndex >= 0 && fLastDrawIndex == fCurrIndex) {
return true;
}
int startIndex = fLastDrawIndex + 1;
if (fLastDrawIndex < 0 || !bm->readyToDraw()) {
// first time
startIndex = 0;
// create bitmap
if (!bm->tryAllocN32Pixels(width, height)) {
return false;
}
// create bitmap for backup
if (!fBackup.tryAllocN32Pixels(width, height)) {
return false;
}
} else if (startIndex > fCurrIndex) {
// rewind to 1st frame for repeat
startIndex = 0;
}
int lastIndex = fCurrIndex;
if (lastIndex < 0) {
// first time
lastIndex = 0;
} else if (lastIndex > fGIF->ImageCount - 1) {
// this block must not be reached.
lastIndex = fGIF->ImageCount - 1;
}
SkColor bgColor = SkPackARGB32(0, 0, 0, 0);
if (gif->SColorMap != nullptr && gif->SBackGroundColor < gif->SColorMap->ColorCount) {
const GifColorType& col = gif->SColorMap->Colors[gif->SBackGroundColor];
bgColor = SkColorSetARGB(0xFF, col.Red, col.Green, col.Blue);
}
// draw each frames - not intelligent way
for (int i = startIndex; i <= lastIndex; i++) {
const SavedImage* cur = &fGIF->SavedImages[i];
if (i == 0) {
bool trans;
int disposal;
getTransparencyAndDisposalMethod(cur, &trans, &disposal);
if (!trans && gif->SColorMap != nullptr) {
fPaintingColor = bgColor;
} else {
fPaintingColor = SkColorSetARGB(0, 0, 0, 0);
}
bm->eraseColor(fPaintingColor);
fBackup.eraseColor(fPaintingColor);
} else {
// Dispose previous frame before move to next frame.
const SavedImage* prev = &fGIF->SavedImages[i-1];
disposeFrameIfNeeded(bm, prev, cur, &fBackup, fPaintingColor);
}
// Draw frame
// We can skip this process if this index is not last and disposal
// method == 2 or method == 3
if (i == lastIndex || !checkIfWillBeCleared(cur)) {
drawFrame(bm, cur, gif->SColorMap);
}
}
// save index
fLastDrawIndex = lastIndex;
return true;
}
///////////////////////////////////////////////////////////////////////////////
Movie* Movie::DecodeStream(SkStreamRewindable* 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) {
// must rewind here, since our construct wants to re-read the data
stream->rewind();
return new GIFMovie(stream);
}
}
return nullptr;
}