tag studio que outofmemory example android animated-gif imagedecoder animated-webp

studio - outofmemory android bitmap



¿Es posible obtener la nueva clase ImageDecoder para devolver Bitmaps, un cuadro tras otro, manualmente? (4)

Bien, tengo una solución posible, usando la biblioteca Glide , junto con la biblioteca GlideWebpDecoder .

No estoy seguro de si esa es la mejor manera de hacerlo, pero creo que debería funcionar bien. El siguiente código muestra cómo es posible realizar el dibujo dibujable en la instancia de Bitmap que creo, para cada fotograma que la animación debe mostrar. No es exactamente lo que pregunté, pero podría ayudar a otros.

Aquí está el código (proyecto disponible here ):

CallbackEx.kt

abstract class CallbackEx : Drawable.Callback { override fun unscheduleDrawable(who: Drawable, what: Runnable) {} override fun invalidateDrawable(who: Drawable) {} override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) {} }

MyAppGlideModule.kt

@GlideModule class MyAppGlideModule : AppGlideModule()

MainActivity.kt

class MainActivity : AppCompatActivity() { var webpDrawable: WebpDrawable? = null var gifDrawable: GifDrawable? = null var callback: Drawable.Callback? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) useFrameByFrameDecoding() // useNormalDecoding() } fun useNormalDecoding() { //webp url : https://res.cloudinary.com/demo/image/upload/fl_awebp/bored_animation.webp Glide.with(this) // .load(R.raw.test) // .load(R.raw.fast) .load(R.raw.example2) // .load("https://res.cloudinary.com/demo/image/upload/fl_awebp/bored_animation.webp") .into(object : SimpleTarget<Drawable>() { override fun onResourceReady(drawable: Drawable, transition: Transition<in Drawable>?) { imageView.setImageDrawable(drawable) when (drawable) { is GifDrawable -> { drawable.start() } is WebpDrawable -> { drawable.start() } } } }) } fun useFrameByFrameDecoding() { //webp url : https://res.cloudinary.com/demo/image/upload/fl_awebp/bored_animation.webp Glide.with(this) .load(R.raw.test) // .load(R.raw.fast) // .load(R.raw.example2) // .load("https://res.cloudinary.com/demo/image/upload/fl_awebp/bored_animation.webp") .into(object : SimpleTarget<Drawable>() { override fun onResourceReady(drawable: Drawable, transition: Transition<in Drawable>?) { // val callback when (drawable) { is GifDrawable -> { gifDrawable = drawable val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) drawable.setBounds(0, 0, bitmap.width, bitmap.height) drawable.setLoopCount(GifDrawable.LOOP_FOREVER) callback = object : CallbackEx() { override fun invalidateDrawable(who: Drawable) { who.draw(canvas) imageView.setImageBitmap(bitmap) Log.d("AppLog", "invalidateDrawable ${drawable.toString().substringAfter(''@'')} ${drawable.frameIndex}/${drawable.frameCount}") } } drawable.callback = callback drawable.start() } is WebpDrawable -> { webpDrawable = drawable val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) drawable.setBounds(0, 0, bitmap.width, bitmap.height) drawable.setLoopCount(WebpDrawable.LOOP_FOREVER) callback = object : CallbackEx() { override fun invalidateDrawable(who: Drawable) { who.draw(canvas) imageView.setImageBitmap(bitmap) Log.d("AppLog", "invalidateDrawable ${drawable.toString().substringAfter(''@'')} ${drawable.frameIndex}/${drawable.frameCount}") } } drawable.callback = callback drawable.start() } } } }) } override fun onStart() { super.onStart() gifDrawable?.start() gifDrawable?.start() } override fun onStop() { super.onStop() Log.d("AppLog", "onStop") webpDrawable?.stop() gifDrawable?.stop() } }

No estoy seguro de por qué SimpleTarget está marcado como obsoleto, y qué debo usar en su lugar, sin embargo.

Usando una técnica similar, también descubrí cómo hacerlo usando ImageDecoder, pero no con la misma funcionalidad por alguna razón. Un proyecto de muestra disponible here .

Aquí está el código:

MainActivity.kt

class MainActivity : AppCompatActivity() { var webpDrawable: AnimatedImageDrawable? = null @SuppressLint("StaticFieldLeak") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val source = ImageDecoder.createSource(resources, R.raw.test) object : AsyncTask<Void, Void, Drawable?>() { override fun doInBackground(vararg params: Void?): Drawable? { return try { ImageDecoder.decodeDrawable(source) } catch (e: Exception) { null } } override fun onPostExecute(drawable: Drawable?) { super.onPostExecute(drawable) // imageView.setImageDrawable(result) if (drawable is AnimatedImageDrawable) { webpDrawable = drawable val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) drawable.setBounds(0, 0, bitmap.width, bitmap.height) drawable.repeatCount = AnimatedImageDrawable.REPEAT_INFINITE drawable.callback = object : Drawable.Callback { val handler = Handler() override fun unscheduleDrawable(who: Drawable, what: Runnable) { Log.d("AppLog", "unscheduleDrawable") } override fun invalidateDrawable(who: Drawable) { who.draw(canvas) imageView.setImageBitmap(bitmap) Log.d("AppLog", "invalidateDrawable") } override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) { Log.d("AppLog", "scheduleDrawable next frame in ${`when` - SystemClock.uptimeMillis()} ms") handler.postAtTime(what, `when`) } } drawable.start() } } }.execute() } override fun onStart() { super.onStart() webpDrawable?.start() } override fun onStop() { super.onStop() webpDrawable?.stop() } }

Fondo

Estoy intentando revisar los mapas de bits de los archivos GIF y WEBP animados manualmente (cuadro por cuadro), para que funcione no solo para las vistas, sino también en otros casos (como un fondo de pantalla en vivo).

El problema

Los archivos GIF / WEBP animados solo son compatibles con Android P, usando ImageDecoder API (ejemplo here ).

Para GIF, quería probar Glide para la tarea, pero he fallado, así que intenté superar esto mediante el uso de una biblioteca que permite cargarlos ( here , la solución here ). Creo que funciona bien.

Para WebP, pensé que había encontrado otra biblioteca que podía funcionar en versiones anteriores de Android ( here , bifurcadas here ), pero parece que no puede manejar bien los archivos WebP en algunos casos (se informa here ). Intenté averiguar cuál es el problema y cómo resolverlo, pero no tuve éxito.

Entonces, asumiendo que algún día Google admitirá la animación GIF y WEBP para versiones anteriores de Android a través de la biblioteca de soporte (lo escribieron here ), decidí intentar usar ImageDecoder para la tarea.

La cosa es que, al mirar la API completa de ImageDecoder, está bastante restringido en la forma en que debemos usarla. No veo cómo puedo superar sus limitaciones.

Lo que he encontrado

Así es como se puede usar ImageDecoder para mostrar un WebP animado en un ImageView (solo una muestra, por supuesto, disponible here ):

class MainActivity : AppCompatActivity() { @SuppressLint("StaticFieldLeak") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val source = ImageDecoder.createSource(resources, R.raw.test) object : AsyncTask<Void, Void, Drawable?>() { override fun doInBackground(vararg params: Void?): Drawable? { return try { ImageDecoder.decodeDrawable(source) } catch (e: Exception) { null } } override fun onPostExecute(result: Drawable?) { super.onPostExecute(result) imageView.setImageDrawable(result) if (result is AnimatedImageDrawable) { result.start() } } }.execute() } }

He intentado leer todas las documentaciones de ImageDecoder y AnimatedImageDrawable , y también mirar su código, pero no veo cómo es posible revisar manualmente cada fotograma y tener el tiempo necesario para esperar entre ellos.

Las preguntas

  1. ¿Hay alguna forma de usar ImageDecoder API para revisar cada cuadro de forma manual, obtener un mapa de bits para dibujar y saber cuánto tiempo se necesita para esperar entre cuadros? Cualquier solución disponible? Tal vez incluso utilizando AnimatedImageDrawable?

  2. Me gustaría hacer lo mismo en versiones anteriores de Android. ¿Es posible? ¿Si es así, cómo? Tal vez en una API / biblioteca diferente? Google escribió que funciona de una manera para usar ImageDecoder en versiones anteriores de Android, pero no veo que se mencione en ninguna parte (excepto el enlace que he proporcionado). Probablemente aún no esté listo ... Android P ni siquiera llegó al 0.1% de los usuarios todavía ... ¿Tal vez Fresco pueda hacerlo? También traté de comprobarlo allí, pero tampoco veo que sea capaz de hacerlo, y es una biblioteca enorme para usar solo para esta tarea, así que prefiero usar una biblioteca diferente en lugar de ... También sé que libwebp está disponible, pero está en C / C ++ y no estoy seguro de si es adecuado para Android, y si hay un puerto para él en Java / Kotlin para Android.

EDITAR:

Ya que creo que obtuve lo que quería, tanto para una biblioteca de terceros como para ImageDecoder, para poder obtener mapas de bits de WebP animado, todavía me gustaría saber cómo obtener el recuento de cuadros y el cuadro actual usando ImageDecoder, si eso es posible. Intenté usar ImageDecoder.decodeDrawable(source, object : ImageDecoder.OnHeaderDecodedListener... , pero no proporciona información de recuento de cuadros, y no hay forma en la API de que pueda ver que puedo ir a un índice de cuadros específico y comience desde allí, o para saber por un cuadro específico cuánto tiempo necesita para pasar al siguiente cuadro, así que hice una pregunta sobre los que están here .

Lamentablemente, tampoco pude encontrar que Google tenga ImageDecoder disponible para versiones anteriores de Android.

También es interesante si hay alguna forma de hacer lo mismo que hice para el archivo de animación relativamente nuevo de HEIC. Actualmente solo es compatible con Android P.


EDIT: En la implementación de esto, encontré un par de problemas inesperados, pero nada insuperable:

  1. AnimatedImageDrawable parece ignorar sus límites configurados. Escale el lienzo en su lugar.
  2. Por razones que no entiendo, AnimatedImageDrawable.draw() ocasionalmente deja de programar el siguiente fotograma. Decidí llamar a la función dos veces. La segunda vez, traduzco el lienzo para que todo el dibujo esté fuera de los límites, lo que debería permitir que la mayor parte del trabajo se optimice.

Aquí está el código de muestra.

import android.annotation.*; import android.graphics.*; import android.graphics.drawable.*; import android.os.*; import android.service.wallpaper.*; import android.support.annotation.*; import android.view.*; @TargetApi(28) public class TestWallpaper extends WallpaperService { @Override public Engine onCreateEngine() { return new Engine(); } private class Engine extends WallpaperService.Engine implements Drawable.Callback { private final Drawable d; private final Handler h = new Handler(); private float scaleX, scaleY; private Engine() { this.setOffsetNotificationsEnabled(false); Drawable d = null; try { d = ImageDecoder .decodeDrawable(ImageDecoder.createSource(getResources(), R.drawable.test)); d.setCallback(this); // AnimatedImageDrawable seems to ignore its configured bounds and use its // intrinsic bounds instead. // In case they fix this bug, we''ll go ahead and request the current // behavior, and then before drawing we''ll transform the canvas to compensate d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); if (d instanceof AnimatedImageDrawable) { final AnimatedImageDrawable anim = (AnimatedImageDrawable) d; anim.setRepeatCount(AnimatedImageDrawable.REPEAT_INFINITE); anim.start(); } } catch (Throwable t) // should never happen { t.printStackTrace(); } this.d = d; } @Override public void invalidateDrawable(@NonNull Drawable _d) { if(isVisible()) draw(getSurfaceHolder().getSurface()); } @Override public void scheduleDrawable(@NonNull Drawable _d, @NonNull Runnable _r, long _at) { if(isVisible()) h.postAtTime(_r, _d, _at); } @Override public void unscheduleDrawable(@NonNull Drawable _d, @NonNull Runnable _r) { h.removeCallbacks(_r, _d); } @Override public void onSurfaceChanged(SurfaceHolder _sh, int _format, int _w, int _h) { scaleX = (float) _w / d.getIntrinsicWidth(); scaleY = (float) _h / d.getIntrinsicHeight(); draw(_sh.getSurface()); } @Override public void onSurfaceRedrawNeeded(SurfaceHolder _sh) { draw(_sh.getSurface()); } private void draw(Surface _s) { try { final Canvas c = _s.lockCanvas(null); c.scale(scaleX, scaleY); d.draw(c); // Sometimes AnimatedImageDrawable neglects to schedule the next frame // after only one draw() of the current frame, so we''ll draw() it again, // but outside the canvas this time c.translate(Float.MAX_VALUE, Float.MAX_VALUE); d.draw(c); // _s.unlockCanvasAndPost(c); } catch (Throwable t) { t.printStackTrace(); // Most likely, the surface was destroyed while we were using it // The new one will be delivered to onSurfaceChanged and we''ll be fine } } @Override public void onVisibilityChanged(boolean _visible) { super.onVisibilityChanged(_visible); if(_visible) draw(getSurfaceHolder().getSurface()); else h.removeCallbacksAndMessages(null); } } }


Jugué con imágenes GIF hace unos años. Mi idea es decodificar imágenes GIF en cuadros, convertir cuadros en mapas de bits, crear, crear dibujos animados de mapas de bits y demorar entre cuadros. Esta es la clase de decodificación:

public class GifDecoder extends Thread { public static final int STATUS_PARSING = 0; public static final int STATUS_FORMAT_ERROR = 1; public static final int STATUS_OPEN_ERROR = 2; public static final int STATUS_FINISH = -1; private static final int MaxStackSize = 4096; public int width; // full image width public int height; // full image height int[] lastPixels; int[] dest; private InputStream in; private int status; private boolean gctFlag; // global color table used private int gctSize; // size of global color table private int loopCount = 1; // iterations; 0 = repeat forever private int[] gct; // global color table private int[] lct; // local color table private int[] act; // active color table private int bgIndex; // background color index private int bgColor; // background color private int lastBgColor; // previous bg color private int pixelAspect; // pixel aspect ratio private boolean lctFlag; // local color table flag private boolean interlace; // interlace flag private int lctSize; // local color table size private int ix, iy, iw, ih; // current image rectangle private int lrx, lry, lrw, lrh; private GifFrame currentFrame = null; private boolean isShow = false; private byte[] block = new byte[256]; // current data block private int blockSize = 0; // block size private int dispose = 0; private int lastDispose = 0; private boolean transparency = false; // use transparent color // max decoder pixel stack size private int delay = 0; // delay in milliseconds private int transIndex; // transparent color index // LZW decoder working arrays private short[] prefix; private byte[] suffix; private byte[] pixelStack; private byte[] pixels; private GifFrame gifFrame; // frames read from current file private int frameCount; private GifAction action = null; private byte[] gifData = null; private int gifDataOffset; private int gifDataLength; private GifDecoder() { } public GifDecoder(byte[] data, GifAction act) { this(data, 0, data.length, act); } public GifDecoder(byte[] data, int offset, int length, GifAction act) { gifData = data; action = act; gifDataOffset = offset; gifDataLength = length; } public GifDecoder(InputStream is, GifAction act) { in = is; action = act; } public void run() { if (in != null) { readStream(); } else if (gifData != null) { readByte(); } } public void free() { GifFrame fg = gifFrame; while (fg != null) { if (fg.image != null) { fg.image.recycle(); } fg.image = null; fg = null; gifFrame = gifFrame.nextFrame; fg = gifFrame; } if (in != null) { try { in.close(); } catch (Exception ex) { } in = null; } gifData = null; } public int getStatus() { return status; } public boolean parseOk() { return status == STATUS_FINISH; } public int getDelay(int n) { delay = -1; if ((n >= 0) && (n < frameCount)) { GifFrame f = getFrame(n); if (f != null) delay = f.delay; } return delay; } public GifFrame getFrame(int n) { GifFrame frame = gifFrame; int i = 0; while (frame != null) { if (i == n) { return frame; } else { frame = frame.nextFrame; } i++; } return null; } public int[] getDelays() { GifFrame f = gifFrame; int[] d = new int[frameCount]; int i = 0; while (f != null && i < frameCount) { d[i] = f.delay; f = f.nextFrame; i++; } return d; } public int getFrameCount() { return frameCount; } public Bitmap getImage() { return getFrameImage(0); } public Bitmap getFrameImage(int n) { GifFrame frame = getFrame(n); if (frame == null) { return null; } else { return frame.image; } } public int getLoopCount() { return loopCount; } public GifFrame getCurrentFrame() { return currentFrame; } public void reset() { currentFrame = gifFrame; } public GifFrame next() { if (isShow == false) { isShow = true; return gifFrame; } else { if (status == STATUS_PARSING) { if (currentFrame.nextFrame != null) currentFrame = currentFrame.nextFrame; //currentFrame = gifFrame; } else { currentFrame = currentFrame.nextFrame; if (currentFrame == null) { currentFrame = gifFrame; } } return currentFrame; } } private Bitmap setPixels() { if (dest == null) dest = new int[width * height]; // fill in starting image contents based on last image''s dispose code if (lastDispose > 0) { if (lastDispose == 3) { // use image before last int n = frameCount - 2; if (n > 0) { Bitmap lastImage = getFrameImage(n - 1); if (lastPixels == null) lastPixels = new int[width * height]; lastImage.getPixels(lastPixels, 0, width, 0, 0, width, height); } else { lastPixels = null; } } if (lastPixels != null) { dest = Arrays.copyOf(lastPixels, lastPixels.length); // copy pixels if (lastDispose == 2) { // fill last image rect area with background color int c = 0; if (!transparency) { c = lastBgColor; } for (int i = 0; i < lrh; i++) { int n1 = (lry + i) * width + lrx; int n2 = n1 + lrw; for (int k = n1; k < n2; k++) { dest[k] = c; } } } } } // copy each source line to the appropriate place in the destination int pass = 1; int inc = 8; int iline = 0; for (int i = 0; i < ih; i++) { int line = i; if (interlace) { if (iline >= ih) { pass++; switch (pass) { case 2: iline = 4; break; case 3: iline = 2; inc = 4; break; case 4: iline = 1; inc = 2; } } line = iline; iline += inc; } line += iy; if (line < height) { int k = line * width; int dx = k + ix; // start of line in dest int dlim = dx + iw; // end of dest line if ((k + width) < dlim) { dlim = k + width; // past dest edge } int sx = i * iw; // start of line in source while (dx < dlim) { // map color and insert in destination int index = ((int) pixels[sx++]) & 0xff; int c = act[index]; if (c != 0) { dest[dx] = c; } dx++; } } } return Bitmap.createBitmap(dest, width, height, Config.ARGB_4444); } private int readByte() { in = new ByteArrayInputStream(gifData, gifDataOffset, gifDataLength); gifData = null; return readStream(); } private int readStream() { init(); if (in != null) { readHeader(); if (!err()) { readContents(); if (frameCount < 0) { status = STATUS_FORMAT_ERROR; action.parseOk(false, -1); } else { status = STATUS_FINISH; action.parseOk(true, -1); } } try { in.close(); } catch (Exception e) { e.printStackTrace(); } } else { status = STATUS_OPEN_ERROR; action.parseOk(false, -1); } return status; } private void decodeImageData() { int NullCode = -1; int npix = iw * ih; int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi; if ((pixels == null) || (pixels.length < npix)) { pixels = new byte[npix]; // allocate new pixel array } if (prefix == null) { prefix = new short[MaxStackSize]; } if (suffix == null) { suffix = new byte[MaxStackSize]; } if (pixelStack == null) { pixelStack = new byte[MaxStackSize + 1]; } // Initialize GIF data stream decoder. data_size = read(); clear = 1 << data_size; end_of_information = clear + 1; available = clear + 2; old_code = NullCode; code_size = data_size + 1; code_mask = (1 << code_size) - 1; for (code = 0; code < clear; code++) { prefix[code] = 0; suffix[code] = (byte) code; } // Decode GIF pixel stream. datum = bits = count = first = top = pi = bi = 0; for (i = 0; i < npix; ) { if (top == 0) { if (bits < code_size) { // Load bytes until there are enough bits for a code. if (count == 0) { // Read a new data block. count = readBlock(); if (count <= 0) { break; } bi = 0; } datum += (((int) block[bi]) & 0xff) << bits; bits += 8; bi++; count--; continue; } // Get the next code. code = datum & code_mask; datum >>= code_size; bits -= code_size; // Interpret the code if ((code > available) || (code == end_of_information)) { break; } if (code == clear) { // Reset decoder. code_size = data_size + 1; code_mask = (1 << code_size) - 1; available = clear + 2; old_code = NullCode; continue; } if (old_code == NullCode) { pixelStack[top++] = suffix[code]; old_code = code; first = code; continue; } in_code = code; if (code == available) { pixelStack[top++] = (byte) first; code = old_code; } while (code > clear) { pixelStack[top++] = suffix[code]; code = prefix[code]; } first = ((int) suffix[code]) & 0xff; // Add a new string to the string table, if (available >= MaxStackSize) { break; } pixelStack[top++] = (byte) first; prefix[available] = (short) old_code; suffix[available] = (byte) first; available++; if (((available & code_mask) == 0) && (available < MaxStackSize)) { code_size++; code_mask += available; } old_code = in_code; } // Pop a pixel off the pixel stack. top--; pixels[pi++] = pixelStack[top]; i++; } for (i = pi; i < npix; i++) { pixels[i] = 0; // clear missing pixels } } private boolean err() { return status != STATUS_PARSING; } private void init() { status = STATUS_PARSING; frameCount = 0; gifFrame = null; gct = null; lct = null; } private int read() { int curByte = 0; try { curByte = in.read(); } catch (Exception e) { status = STATUS_FORMAT_ERROR; } return curByte; } private int readBlock() { blockSize = read(); int n = 0; if (blockSize > 0) { try { int count = 0; while (n < blockSize) { count = in.read(block, n, blockSize - n); if (count == -1) { break; } n += count; } } catch (Exception e) { e.printStackTrace(); } if (n < blockSize) { status = STATUS_FORMAT_ERROR; } } return n; } private int[] readColorTable(int ncolors) { int nbytes = 3 * ncolors; int[] tab = null; byte[] c = new byte[nbytes]; int n = 0; try { n = in.read(c); } catch (Exception e) { e.printStackTrace(); } if (n < nbytes) { status = STATUS_FORMAT_ERROR; } else { tab = new int[256]; // max size to avoid bounds checks int i = 0; int j = 0; while (i < ncolors) { int r = ((int) c[j++]) & 0xff; int g = ((int) c[j++]) & 0xff; int b = ((int) c[j++]) & 0xff; tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b; } } return tab; } private void readContents() { // read GIF file content blocks boolean done = false; while (!(done || err())) { int code = read(); switch (code) { case 0x2C: // image separator readImage(); break; case 0x21: // extension code = read(); switch (code) { case 0xf9: // graphics control extension readGraphicControlExt(); break; case 0xff: // application extension readBlock(); String app = ""; for (int i = 0; i < 11; i++) { app += (char) block[i]; } if (app.equals("NETSCAPE2.0")) { readNetscapeExt(); } else { skip(); // don''t care } break; default: // uninteresting extension skip(); } break; case 0x3b: // terminator done = true; break; case 0x00: // bad byte, but keep going and see what happens break; default: status = STATUS_FORMAT_ERROR; } } } private void readGraphicControlExt() { read(); // block size int packed = read(); // packed fields dispose = (packed & 0x1c) >> 2; // disposal method if (dispose == 0) { dispose = 1; // elect to keep old image if discretionary } transparency = (packed & 1) != 0; delay = readShort() * 10; // delay in milliseconds transIndex = read(); // transparent color index read(); // block terminator } private void readHeader() { String id = ""; for (int i = 0; i < 6; i++) { id += (char) read(); } if (!id.startsWith("GIF")) { status = STATUS_FORMAT_ERROR; return; } readLSD(); if (gctFlag && !err()) { gct = readColorTable(gctSize); bgColor = gct[bgIndex]; } } private void readImage() { ix = readShort(); // (sub)image position & size iy = readShort(); iw = readShort(); ih = readShort(); int packed = read(); lctFlag = (packed & 0x80) != 0; // 1 - local color table flag interlace = (packed & 0x40) != 0; // 2 - interlace flag // 3 - sort flag // 4-5 - reserved lctSize = 2 << (packed & 7); // 6-8 - local color table size if (lctFlag) { lct = readColorTable(lctSize); // read table act = lct; // make local table active } else { act = gct; // make global table active if (bgIndex == transIndex) { bgColor = 0; } } int save = 0; if (transparency) { save = act[transIndex]; act[transIndex] = 0; // set transparent color if specified } if (act == null) { status = STATUS_FORMAT_ERROR; // no color table defined } if (err()) { return; } try { decodeImageData(); // decode pixel data skip(); if (err()) { return; } frameCount++; // create new image to receive frame data // createImage(width, height); Bitmap image = setPixels(); // transfer pixel data to image if (gifFrame == null) { gifFrame = new GifFrame(image, delay); currentFrame = gifFrame; } else { GifFrame f = gifFrame; while (f.nextFrame != null) { f = f.nextFrame; } f.nextFrame = new GifFrame(image, delay); } // frames.addElement(new GifFrame(image, delay)); // add image to frame // list if (transparency) { act[transIndex] = save; } resetFrame(); if (!action.parseOk(true, frameCount)) { status = STATUS_FINISH; return; } } catch (OutOfMemoryError e) { Log.e("GifDecoder", ">>> log : " + e.toString()); e.printStackTrace(); } } private void readLSD() { // logical screen size width = readShort(); height = readShort(); // packed fields int packed = read(); gctFlag = (packed & 0x80) != 0; // 1 : global color table flag // 2-4 : color resolution // 5 : gct sort flag gctSize = 2 << (packed & 7); // 6-8 : gct size bgIndex = read(); // background color index pixelAspect = read(); // pixel aspect ratio } private void readNetscapeExt() { do { readBlock(); if (block[0] == 1) { // loop count sub-block int b1 = ((int) block[1]) & 0xff; int b2 = ((int) block[2]) & 0xff; loopCount = (b2 << 8) | b1; } } while ((blockSize > 0) && !err()); } private int readShort() { // read 16-bit value, LSB first return read() | (read() << 8); } private void resetFrame() { lastDispose = dispose; lrx = ix; lry = iy; lrw = iw; lrh = ih; lastPixels = dest; lastBgColor = bgColor; dispose = 0; transparency = false; delay = 0; lct = null; } /** * Skips variable length blocks up to and including next zero length block. */ private void skip() { do { readBlock(); } while ((blockSize > 0) && !err()); }

}

Subo la fuente de demostración completa here . Espero que te ayude.


ver ImageDecoder.Source ...

uno necesita crear primero una fuente, ya sea con:

// source from file val source = ImageDecoder.createSource(file) // source from byte buffer val source = ImageDecoder.createSource(byteBuffer) // source from resource val source = ImageDecoder.createSource(resources, resId) // source from URI val source = ImageDecoder.createSource(contentResolver, uri) // source from asset file val source = ImageDecoder.createSource(assetManager, assetFileName)

y luego decodificar, ya sea con:

// create bitmap val bitmap = ImageDecoder.decodeBitmap(source) // create drawable val drawable = ImageDecoder.decodeDrawable(source)

actualización : el problema es que el AnimatedImageDrawable resultante no tiene los dos métodos: getNumberOfFrames() y getFrame(int) como tiene un AnimationDrawable . como señaló @androiddeveloper ... He arruinado dos clases diferentes. He revisado la documentación y parece que no hay manera. con GIFImageReader todavía se puede extraer ( source ):

ArrayList<BufferedImage> getFrames(File gif) throws IOException { ArrayList<BufferedImage> frames = new ArrayList<BufferedImage>(); ImageReader ir = new GIFImageReader(new GIFImageReaderSpi()); ir.setInput(ImageIO.createImageInputStream(gif)); for(int i = 0; i < ir.getNumImages(true); i++) { frames.add(ir.read(i)); } return frames; }

Intenté convertirlo a Kotlin, pero javax.imageio.ImageIO no está disponible en Android.