reduce gif memory usage

This commit is contained in:
houzh 2023-11-30 10:27:59 +08:00
parent 91006c3f92
commit 41dd98e959
5 changed files with 46 additions and 304 deletions

View File

@ -77,22 +77,25 @@ int AnimatedImageDrawable::getAlpha()const{
constexpr int FINISHED=-1;
void AnimatedImageDrawable::draw(Canvas& canvas){
if (mStarting) {
mStarting = false;
if (!mStarting) {
mStarting = true;
postOnAnimationStart();
}
canvas.save();
Cairo::RefPtr<Cairo::ImageSurface>image = mAnimatedImageState->mImage;
ImageDecoder*mDecoder = mAnimatedImageState->mDecoder;
const long nextDelay = mDecoder->getFrameDuration(mCurrentFrame);
mDecoder->readImage(image,mCurrentFrame);
const long nextDelay = mDecoder->getFrameDuration(mCurrentFrame);
// a value <= 0 indicates that the drawable is stopped or that renderThread
// will manage the animation
LOGV("%p draw Frame %d/%d nextDelay=%d",this,mCurrentFrame,mAnimatedImageState->mFrameCount,nextDelay);
if(mStarting){
if (nextDelay > 0) {
if (mRunnable == nullptr) {
mRunnable = std::bind(&AnimatedImageDrawable::invalidateSelf,this);
mRunnable = [this](){
invalidateSelf();
mCurrentFrame=(mCurrentFrame+1)%mAnimatedImageState->mFrameCount;
};
}
scheduleSelf(mRunnable, nextDelay + SystemClock::uptimeMillis());
} else if (nextDelay<=0){// == FINISHED) {

View File

@ -22,46 +22,7 @@ APNGDecoder::APNGDecoder() {
APNGDecoder::~APNGDecoder() {
}
int APNGDecoder::readImage(Cairo::RefPtr<Cairo::ImageSurface>image,int& frameIndex) {
if(!mFrames.empty()){
ImageFrame*frm = mFrames[frameIndex];
#if 1
const int dststride = image->get_stride();
const int srcstride = frm->width*4;
int u, v, al;
const int bop= frm->disposalMethod&0xFF;
uint8_t* dst = (image->get_data() + frm->y*dststride + frm->x*4);
uint8_t* src = frm->pixels;
for(int y=0;y<frm->height;y++){
uint8_t*dp = dst;
uint8_t*sp = src;
for(int x=0;x<frm->width;x++,dp+=4,sp+=4){
if((sp[3]==255)||(dp[3]==0)||(bop==APNG_BLEND_OP_SOURCE)||(frameIndex==0))*(uint32_t*)dp= *(uint32_t*)sp;
else if(sp[3]!=0){
u = sp[3]*255;
v = (255-sp[3])*dp[3];
al = 255*255-(255-sp[3])*(255-dp[3]);
dp[0] = (sp[0]*u + dp[0]*v)/al;
dp[1] = (sp[1]*u + dp[1]*v)/al;
dp[2] = (sp[2]*u + dp[2]*v)/al;
dp[3] = al/255;
}
}
dst += dststride;
src += srcstride;
}
#else
Cairo::RefPtr<Cairo::Context>ctx = Cairo::Context::create(image);
Cairo::RefPtr<Cairo::ImageSurface>frame=Cairo::ImageSurface::create(frm->pixels,Cairo::Surface::Format::ARGB32,int(frm->width),int(frm->height),int(frm->width*4));
ctx->set_operator(((frm->disposalMethod&0xFF)&&(frameIndex))?Cairo::Context::Operator::OVER:Cairo::Context::Operator::SOURCE);
ctx->set_source(frame,frm->x,frm->y);
ctx->rectangle(frm->x,frm->y,frm->width,frm->height);
ctx->fill();
#endif
LOGV("FRAME[%d](%d,%d,%d,%d)disposalMethod=%x delay=%d",frameIndex,frm->x,frm->y,frm->width,frm->height,frm->disposalMethod,frm->duration);
frameIndex = (frameIndex+1)%mFrameCount;
return frm->duration;
}
int APNGDecoder::readImage(Cairo::RefPtr<Cairo::ImageSurface>image,int frameIndex) {
return 0;
}
@ -72,104 +33,6 @@ static void istream_png_reader(png_structp png_ptr, png_bytep png_data, png_size
};
int APNGDecoder::load(std::istream&istream) {
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr) {
LOGE("Error creating read struct");
return false;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
LOGE("Error creating info struct");
png_destroy_read_struct(&png_ptr, NULL, NULL);
return false;
}
if (setjmp(png_jmpbuf(png_ptr))) {
LOGE("Error during libpng init_io");
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return false;
}
png_set_read_fn(png_ptr,(void*)&istream,istream_png_reader);
png_read_info(png_ptr, info_ptr);
int bit_depth, color_type;
png_get_IHDR(png_ptr, info_ptr,(uint32_t*)&mImageWidth, (uint32_t*)&mImageHeight, &bit_depth, &color_type, NULL, NULL, NULL);
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png_ptr);
}
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
png_set_expand_gray_1_2_4_to_8(png_ptr);
}
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(png_ptr);
}
if (bit_depth == 16) {
png_set_strip_16(png_ptr);
}
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
png_set_gray_to_rgb(png_ptr);
}
png_set_bgr(png_ptr);
png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
png_read_update_info(png_ptr, info_ptr);
color_type = png_get_color_type(png_ptr, info_ptr);
mFrameCount = png_get_num_frames(png_ptr, info_ptr);
mFrames.reserve(mFrameCount);
uint32_t imgBytes = 0;
ImageFrame*snap = new ImageFrame;
snap->x = snap->y = 0;snap->disposalMethod = 0;
snap->width = mImageWidth ; snap->height = mImageHeight;
snap->pixels= new uint8_t[mImageWidth*mImageHeight*4];
auto tm1=SystemClock::currentTimeMillis();
for (png_uint_32 ifrm = 0; ifrm < mFrameCount; ++ifrm) {
png_uint_16 delay_num , delay_den;
png_byte dispose_op , blend_op;
ImageFrame* frame = new ImageFrame;
png_read_frame_head(png_ptr, info_ptr);
png_get_next_frame_fcTL(png_ptr, info_ptr, &frame->width, &frame->height, &frame->x, &frame->y,
&delay_num, &delay_den, &dispose_op, &blend_op);
frame->disposalMethod = (dispose_op<<8)|blend_op;
frame->duration = delay_num * 1000 / (delay_den ? delay_den : 1000);// Convert to milliseconds
frame->pixels = new uint8_t[frame->width * frame->height * 4];
std::vector<png_bytep> row_pointers(frame->height);
imgBytes += frame->width * frame->height * 4;
if(ifrm==0){
memset(frame->pixels,0,frame->width * frame->height * 4);
}else{
switch(snap->disposalMethod>>8){
case APNG_DISPOSE_OP_PREVIOUS:memcpy(frame->pixels,snap->pixels,frame->width*frame->height*4);break;
case APNG_DISPOSE_OP_BACKGROUND:memset(frame->pixels,0,frame->width * frame->height * 4);break;
case APNG_DISPOSE_OP_NONE:break;
}
}
for (png_uint_32 y = 0; y < frame->height; ++y) {
row_pointers[y] = frame->pixels + y* frame->width * 4;
}
png_read_image(png_ptr, row_pointers.data());
if((dispose_op==APNG_DISPOSE_OP_PREVIOUS)&&((snap->disposalMethod>>8)!=APNG_DISPOSE_OP_PREVIOUS)){
memcpy(snap->pixels,frame->pixels,frame->width * frame->height * 4);
}
LOGV("FRAME[%d](%d,%d,%d,%d)op=%d/%d",ifrm,frame->x,frame->y,frame->width,frame->height,dispose_op,blend_op);
snap->disposalMethod=(dispose_op<<8)|blend_op;
mFrames.push_back(frame);
}
auto tm2=SystemClock::currentTimeMillis();
LOGD("image(%dx%d)%d frames %d bytes color_type=%d/%d/%d time=%dms",mImageWidth,mImageHeight,mFrameCount,imgBytes,
color_type,PNG_COLOR_TYPE_RGB,PNG_COLOR_TYPE_RGB_ALPHA,tm2-tm1);
delete []snap->pixels;
delete snap;
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return mFrameCount;
return 0;
}
}/*namesapce*/

View File

@ -7,19 +7,21 @@
namespace cdroid{
typedef struct {
std::vector<int>delays;
GifFileType*gif;
}GIFPrivate;
GIFDecoder::GIFDecoder(){
GIFPrivate*priv = new GIFPrivate;
priv->gif=nullptr;
mPrivate=priv;
}
static int GIFRead(GifFileType *gifFile, GifByteType *buff, int rdlen){
std::istream*is=(std::istream*)gifFile->UserData;
is->read((char*)buff,rdlen);
return is->gcount();
}
static int gifDrawFrame(GifFileType*gif,int&current_frame,size_t pxstride,uint8_t *pixels,bool force_DISPOSE_1);
GIFDecoder::~GIFDecoder(){
DGifCloseFile((GifFileType*)mPrivate,nullptr);
GIFPrivate*priv=(GIFPrivate*)mPrivate;
DGifCloseFile((GifFileType*)priv->gif,nullptr);
delete priv;
}
#define ARGB(a, r, g, b) ( ((a) & 0xff) << 24 ) | ( ((r) & 0xff) << 16 ) | ( ((g) & 0xff) << 8 ) | ((b) & 0xff)
@ -28,96 +30,14 @@ GIFDecoder::~GIFDecoder(){
#define TRANSPARENCY(ext) ((ext)->Bytes[0] & 1)
#define DELAY(ext) (10*((ext)->Bytes[2] << 8 | (ext)->Bytes[1]))
#if 10
void GIFDecoder::setFramePixels(int frameIndex){
int x,y;
ImageFrame*frame = mFrames[frameIndex];
const int pxstride = frame->width*4;
GifFileType*gif = (GifFileType*)mPrivate;
SavedImage *gifFrame = &gif->SavedImages[frameIndex];
const GifImageDesc *frameInfo=&(gifFrame->ImageDesc);
ColorMapObject *colorMap=(frameInfo->ColorMap?frameInfo->ColorMap:gif->SColorMap);
GifColorType* bg = &colorMap->Colors[gif->SBackGroundColor];
GifColorType* color = nullptr;
ExtensionBlock *ext = nullptr;
uint8_t* pixels= frame->pixels;
uint32_t* line = nullptr;
for (int j = 0; j < gifFrame->ExtensionBlockCount; j++) {
if (gifFrame->ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE) {
ext = &(gifFrame->ExtensionBlocks[j]);
break;
}
}
frame->duration = DELAY(ext);
frame->disposalMethod = DISPOSE(ext);
// For DISPOSE = 1, we assume its been drawn
if (ext && DISPOSE(ext) == 1 /*&& force_DISPOSE_1*/ && frameIndex > 0) {
//gifDrawFrame(gif,frameIndex-1,pxstride, pixels, true);
} else if (ext && DISPOSE(ext) == 2 && bg) {
uint8_t*px= frame->pixels;
for (y = 0; y < frame->height; y++) {
line = (uint32_t *) px;
for (x = 0; x < frame->width; x++) {
line[x] = ARGB(255, bg->Red, bg->Green, bg->Blue);
}
px = (px + pxstride);
}
//current_frame=(current_frame+1)%gif->ImageCount;
} else if (ext && DISPOSE(ext) == 3 && frameIndex > 1) {
//gifDrawFrame(gif,frameIndex-2,pxstride, pixels, true);
}//else current_frame=(current_frame+1)%gif->ImageCount;
const bool isTransparent = TRANSPARENCY(ext);
const int transparentIndex = TRANS_INDEX(ext);
if (frameInfo->Interlace) {
int n = 0, inc = 8, p = 0,loc=0;
uint8_t* px = frame->pixels;
for (y = 0; y < frameInfo->Height; y++) {
for (x = 0; x < frameInfo->Width; x++) {
loc = y * frameInfo->Width + x;
if (ext && isTransparent && (gifFrame->RasterBits[loc] == transparentIndex)) {
continue;
}
color = (ext && gifFrame->RasterBits[loc] == TRANS_INDEX(ext)) ? bg
: &colorMap->Colors[gifFrame->RasterBits[loc]];
if (color)
line[x] = ARGB(255, color->Red, color->Green, color->Blue);
}
px = (px + pxstride * inc);
n += inc;
if (n >= frameInfo->Height) {
n = 0;
switch (p) {
case 0:
px = (pixels + pxstride * (4/*+ frameInfo->Top*/));
inc = 8; p++; break;
case 1:
px = (pixels + pxstride * (2/*+ frameInfo->Top*/));
inc = 4; p++; break;
case 2:
px = (pixels + pxstride * (1/*+ frameInfo->Top*/));
inc = 2; p++; break;
}
}
}
} else {
uint8_t* px = frame->pixels;//( px + pxstride * frameInfo->Top);
for (y = 0; y < frameInfo->Height; y++) {
line = (uint32_t *) px;
for (x = 0; x < frameInfo->Width; x++) {
int loc = y * frameInfo->Width + x;
if (ext && isTransparent && (gifFrame->RasterBits[loc] == transparentIndex)) {
continue;
}
color = (ext && gifFrame->RasterBits[loc] == TRANS_INDEX(ext)) ? bg
: &colorMap->Colors[gifFrame->RasterBits[loc]];
if (color)
line[x] = ARGB(255, color->Red, color->Green, color->Blue);
}
px +=pxstride;
}
}
static int GIFRead(GifFileType *gifFile, GifByteType *buff, int rdlen){
std::istream*is=(std::istream*)gifFile->UserData;
is->read((char*)buff,rdlen);
return is->gcount();
}
#endif
static int gifDrawFrame(GifFileType*gif,int&current_frame,size_t pxstride,uint8_t *pixels,bool force_DISPOSE_1);
int GIFDecoder::load(std::istream&is){
int err;
GifFileType*gifFileType = DGifOpen(&is,GIFRead,&err);
@ -126,47 +46,25 @@ int GIFDecoder::load(std::istream&is){
DGifSlurp(gifFileType);
mFrameCount = gifFileType->ImageCount;
mPrivate = gifFileType;
((GIFPrivate*)mPrivate)->gif = gifFileType;
mImageWidth = gifFileType->SWidth;
mImageHeight= gifFileType->SHeight;
for(int i=0 ; i < mFrameCount;i++){
SavedImage *frame = &(gifFileType->SavedImages[i]);
GifImageDesc *frameInfo=&(frame->ImageDesc);
ImageDecoder::ImageFrame*frm=new ImageDecoder::ImageFrame();
frm->x = frameInfo->Left;
frm->y = frameInfo->Top;
frm->width = frameInfo->Width;
frm->height= frameInfo->Height;
frm->pixels= new unsigned char[frm->width*frm->height*4];
mFrames.push_back(frm);
setFramePixels(i);
LOGV("frame[%d](%d,%d,%d,%d)disposalMethod=%d delay=%d",i,frm->x,frm->y,frm->width,frm->height,frm->disposalMethod,frm->duration);
}
LOGD("GIF %d frames loaded size(%dx%d)",mFrameCount,mImageWidth,mImageHeight);
return mFrameCount;
}
int GIFDecoder::readImage(Cairo::RefPtr<Cairo::ImageSurface>image,int& frameIndex){
if(!mFrames.empty()){
ImageFrame*frm = mFrames[frameIndex];
const int dststride = image->get_stride();
const int srcstride = frm->width*4;
uint8_t* dst = (image->get_data() + frm->y*dststride + frm->x*4);
uint8_t* src = frm->pixels;
for(int y=0;y<frm->height;y++){
uint32_t*ld =(uint32_t*)dst;
uint32_t*ls =(uint32_t*)src;
for(int x=0;x<frm->width;x++)
ld[x]= ls[x];
dst += dststride;
src += srcstride;
}
frameIndex = (frameIndex+1)%mFrameCount;
image->mark_dirty();
return frm->duration;
int GIFDecoder::readImage(Cairo::RefPtr<Cairo::ImageSurface>image,int frameIndex){
GIFPrivate*priv=(GIFPrivate*)mPrivate;
const int duration=gifDrawFrame(priv->gif,frameIndex,image->get_stride(),image->get_data(),false);
if(priv->delays.size()<frameIndex+1){
priv->delays.push_back(duration);
}
return gifDrawFrame((GifFileType*)mPrivate,frameIndex,image->get_stride(),image->get_data(),false);
return duration;
}
int GIFDecoder::getFrameDuration(int frameIndex)const{
GIFPrivate*priv=(GIFPrivate*)mPrivate;
return priv->delays.at(frameIndex);
}
static int gifDrawFrame(GifFileType*gif,int&current_frame,size_t pxstride,uint8_t *pixels,bool force_DISPOSE_1) {

View File

@ -18,10 +18,6 @@ ImageDecoder::ImageDecoder(){
}
ImageDecoder::~ImageDecoder(){
for(auto f:mFrames){
delete []f->pixels;
delete f;
}
}
int ImageDecoder::getWidth()const{
@ -37,7 +33,7 @@ int ImageDecoder::getFrameCount()const{
}
int ImageDecoder::getFrameDuration(int idx)const{
int duration = ( (idx>=0) && (idx<mFrameCount) ) ? mFrames[idx]->duration:INT_MAX;
int duration = 0;//( (idx>=0) && (idx<mFrameCount) ) ? mFrames[idx]->duration:INT_MAX;
// Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
// We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
// a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>

View File

@ -9,38 +9,20 @@
namespace cdroid{
class ImageDecoder{
public:
enum DisposalMethod{
Unspecified,
DoNotDispose,
RestoreToBackground,
RestoreToPrevious
};
struct ImageFrame {
unsigned char * pixels;
unsigned int x;
unsigned int y;
unsigned int width;
unsigned int height;
unsigned int duration;
unsigned int disposalMethod;
};
protected:
void*mPrivate;
int mFrameCount;
int mImageWidth;
int mImageHeight;
std::vector<ImageFrame*>mFrames;
public:
ImageDecoder();
virtual ~ImageDecoder();
virtual int load(std::istream&)=0;
int getWidth()const;
int getHeight()const;
int getFrameCount()const;
int getFrameDuration(int)const;
virtual int readImage(Cairo::RefPtr<Cairo::ImageSurface>image,int& frameIndex)=0;
virtual int getFrameCount()const;
virtual int getFrameDuration(int)const;
virtual int readImage(Cairo::RefPtr<Cairo::ImageSurface>image,int frameIndex)=0;
static ImageDecoder*create(Context*ctx,const std::string&resourceId);
static Drawable*createAsDrawable(Context*ctx,const std::string&resourceId);
};
@ -50,8 +32,8 @@ public:
GIFDecoder();
~GIFDecoder()override;
int load(std::istream&)override;
int readImage(Cairo::RefPtr<Cairo::ImageSurface>image,int& frameIndex)override;
void setFramePixels(int frameIndex);
virtual int getFrameDuration(int)const;
int readImage(Cairo::RefPtr<Cairo::ImageSurface>image,int frameIndex)override;
};
class APNGDecoder:public ImageDecoder{
@ -59,7 +41,7 @@ public:
APNGDecoder();
~APNGDecoder()override;
int load(std::istream&)override;
int readImage(Cairo::RefPtr<Cairo::ImageSurface>image,int& frameIndex)override;
int readImage(Cairo::RefPtr<Cairo::ImageSurface>image,int frameIndex)override;
};
class WebpDecoder:public ImageDecoder{
@ -67,7 +49,7 @@ public:
WebpDecoder();
~WebpDecoder();
int load(std::istream&)override;
int readImage(Cairo::RefPtr<Cairo::ImageSurface>image,int& frameIndex)override;
int readImage(Cairo::RefPtr<Cairo::ImageSurface>image,int frameIndex)override;
};
}/*endof namespace*/
#endif