Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  package com.koushikdutta.ion;
  
  import  android.content.res.Resources;
  import  android.graphics.Bitmap;
  import  android.graphics.Canvas;
  import  android.graphics.Color;
  import  android.graphics.ColorFilter;
  import  android.graphics.Paint;
  import  android.graphics.PixelFormat;
 import  android.graphics.Rect;
 import  android.graphics.drawable.BitmapDrawable;
 import  android.graphics.drawable.Drawable;
 import  android.graphics.drawable.LayerDrawable;
 import  android.os.Looper;
 import  android.os.SystemClock;
 import  android.text.TextUtils;
 import  android.widget.ImageView;
 
 
Created by koush on 6/8/13.
 
 class IonDrawable extends LayerDrawable {
     private Paint paint;
     private BitmapInfo info;
     private int placeholderResource;
     private Drawable placeholder;
     private int errorResource;
     private Drawable error;
     private Resources resources;
     private int loadedFrom;
     private boolean fadeIn;
     private int resizeWidth;
     private int resizeHeight;
     private boolean repeatAnimation;
     private Ion ion;
     private BitmapFetcher bitmapFetcher;
     private IonDrawableCallback callback;
     private IonGifDecoder gifDecoder;
 
         return ;
     }
 
     public IonDrawable setLoadCallback(FutureCallback<IonDrawableloadCallback) {
         this. = loadCallback;
         return this;
     }
 
     public IonDrawable ion(Ion ion) {
         if (ion == null)
             throw new AssertionError("null ion");
         this. = ion;
         return this;
     }
 
     public Drawable getCurrentDrawable() {
         if ( == null) {
             if ( != 0)
                 return .getDrawable();
         }
         if ( != null) {
             if (. != null)
                 return new BitmapDrawable(.);
             else if (. != null) {
                 GifFrame last = ..getLastFrame();
                 if (last != null)
                     return new BitmapDrawable(last.image);
                 if ( != 0)
                     return .getDrawable();
                 return null;
             }
         }
         if ( != 0)
             return .getDrawable();
         return null;
     }
 
     public BitmapInfo getBitmapInfo() {
         return ;
     }
 
     // create an internal static class that can act as a callback.
     // dont let it hold strong references to anything.
     static class IonDrawableCallback implements FutureCallback<BitmapInfo> {
         private WeakReference<IonDrawableionDrawableRef;
         private String bitmapKey;
         private Ion ion;
         public IonDrawableCallback(IonDrawable drawable) {
              = new WeakReference<IonDrawable>(drawable);
 //            imageViewRef = new ContextReference.ImageViewContextReference(imageView);
        }
        public void register(Ion ionString bitmapKey) {
            String previousKey = this.;
            Ion previousIon = this.;
            if (TextUtils.equals(previousKeybitmapKey) && this. == ion)
                return;
            this. = ion;
            this. = bitmapKey;
            if (ion != null)
                ion.bitmapsPending.add(bitmapKeythis);
            unregister(previousIonpreviousKey);
        }
        private void unregister(Ion ionString key) {
            if (key == null)
                return;
            // unregister this drawable from the bitmaps that are
            // pending.
            // if this drawable was the only thing waiting for this bitmap,
            // then the removeItem call will return the TransformBitmap/LoadBitmap instance
            // that was providing the result.
            if (ion.bitmapsPending.removeItem(keythis)) {
                // find out who owns this thing, to see if it is a candidate for removal
                Object owner = ion.bitmapsPending.tag(key);
                if (owner instanceof TransformBitmap) {
                    TransformBitmap info = (TransformBitmap)owner;
                    ion.bitmapsPending.remove(info.key);
                    // this transform is also backed by a LoadBitmap* or a DeferredLoadBitmap, grab that
                    // if it is the only waiter
                    if (ion.bitmapsPending.removeItem(info.downloadKeyinfo))
                        owner = ion.bitmapsPending.tag(info.downloadKey);
                }
                // only cancel deferred loads... LoadBitmap means a download is already in progress.
                // due to view recycling, cancelling that may be bad, as it may be rerequested again
                // during the recycle process.
                if (owner instanceof DeferredLoadBitmap) {
                    DeferredLoadBitmap defer = (DeferredLoadBitmap)owner;
                    ion.bitmapsPending.remove(defer.key);
                }
            }
            ion.processDeferred();
        }
        @Override
        public void onCompleted(Exception eBitmapInfo result) {
            assert Thread.currentThread() == Looper.getMainLooper().getThread();
            assert result != null;
            // see if the imageview is still alive and cares about this result
            IonDrawable drawable = .get();
            if (drawable == null)
                return;
            drawable.setBitmap(resultresult.loadedFrom);
            FutureCallback<IonDrawablecallback = drawable.loadCallback;
            if (callback != null)
                callback.onCompleted(edrawable);
        }
    }
    class IonGifDecoder {
        GifDecoder gifDecoder;
        Exception exception;
        GifFrame currentFrame;
        long nextFrameRender;
        public IonGifDecoder(BitmapInfo info){
             = info.gifDecoder.mutate();
             = .getLastFrame();
        }
        Runnable loader = new Runnable() {
            @Override
            public void run() {
                try {
                    .nextFrame();
                }
                catch (Exception e) {
                     = e;
                }
                ..post();
            }
        };
        Runnable postLoad = new Runnable() {
            @Override
            public void run() {
                 = false;
                invalidateSelf();
            }
        };
        long getDelay() {
            long delay = .;
            if (delay == 0)
                delay = 1000 / 10;
            return delay;
        }
        public GifFrame getCurrentFrame() {
            long now = System.currentTimeMillis();
            if ( == 0) {
                 = now + getDelay();
                scheduleNextFrame();
            }
            if (now >= ) {
                // see if a frame is available
                if (.getLastFrame() != ) {
                    // we have a frame waiting, grab it i guess.
                     = .getLastFrame();
                     += getDelay();
                }
                scheduleNextFrame();
            }
            return ;
        }
        boolean isLoading;
        public synchronized void scheduleNextFrame() {
            if ()
                return;
            if ( != null)
                return;
            if (.getStatus() == . && )
                .restart();
             = true;
            Ion.getBitmapLoadExecutorService().execute();
        }
    }
    public IonDrawable setFadeIn(boolean fadeIn) {
        this. = fadeIn;
        return this;
    }
    public IonDrawable setBitmapFetcher(BitmapFetcher bitmapFetcher) {
        this. = bitmapFetcher;
        if ( == null)
            throw new AssertionError("null ion");
        return this;
    }
    public void cancel() {
        .register(nullnull);
         = null;
    }
    private final static Drawable null0 = new BitmapDrawable((Bitmap)null);
    private final static Drawable null1 = new BitmapDrawable((Bitmap)null);
    private final static Drawable null2 = new BitmapDrawable((Bitmap)null);
    private static final int DEFAULT_PAINT_FLAGS = Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
    public IonDrawable(Resources resources) {
        super(new Drawable[] {  });
        setId(0, 0);
        setId(1, 1);
        setId(2, 2);
        this. = resources;
         = new Paint();
         = new IonDrawableCallback(this);
    }
    private int textureDim;
    private int maxLevel;
    public IonDrawable setBitmap(BitmapInfo infoint loadedFrom) {
        if (this. == info)
            return this;
        cancel();
        this. = loadedFrom;
        this. = info;
         = null;
         = null;
        setDrawableByLayerId(2, );
        invalidateSelf();
        tryGetBitmapResource();
        if (info == null)
            return this;
        if (info.decoder != null) {
            // find number of tiles across to fit
            double wlevel = (double)info.originalSize.x / ;
            double hlevel = (double)info.originalSize.y / ;
            // find the level: find how many power of 2 tiles are necessary
            // to fit the entire image. ie, fit it into a square.
            double level = Math.max(wlevelhlevel);
            level = Math.log(level) / ;
             = (int)Math.ceil(level);
            // now, we know the entire image will fit in a square image of
            // this dimension:
             =  << ;
        }
        else if (info.gifDecoder != null) {
             = new IonGifDecoder(info);
        }
        return this;
    }
    public IonDrawable setRepeatAnimation(boolean repeatAnimation) {
        this. = repeatAnimation;
        return this;
    }
    public IonDrawable setSize(int resizeWidthint resizeHeight) {
        if (this. == resizeWidth && this. == resizeHeight)
            return this;
        this. = resizeWidth;
        this. = resizeHeight;
        invalidateSelf();
        return this;
    }
    public IonDrawable setError(int resource, Drawable drawable) {
        if ((drawable != null && drawable == ) || (resource != 0 && resource == ))
            return this;
         = resource;
         = drawable;
        if ( != null)
            setDrawableByLayerId(1, );
        else
            setDrawableByLayerId(1, );
        invalidateSelf();
        return this;
    }
    public IonDrawable setPlaceholder(int resource, Drawable drawable) {
        if ((drawable != null && drawable == ) || (resource != 0 && resource == ))
            return this;
         = resource;
         = drawable;
        if ( != null)
            setDrawableByLayerId(0, );
        else
            setDrawableByLayerId(0, );
        invalidateSelf();
        return this;
    }
    private Drawable tryGetErrorResource() {
        if ( != null)
            return ;
        if ( == 0)
            return null;
         = .getDrawable();
        if ( != null)
            setDrawableByLayerId(1, );
        return ;
    }
    BitmapDrawable bitmapDrawable;
    private BitmapDrawable tryGetBitmapResource() {
        if ( != null)
            return ;
        if ( == null)
            return null;
        if (. == null)
            return null;
         = new BitmapDrawable(.);
        setDrawableByLayerId(2, );
        return ;
    }
    private Drawable tryGetPlaceholderResource() {
        if ( != null)
            return ;
        if ( == 0)
            return null;
         = .getDrawable();
        if ( != null)
            setDrawableByLayerId(0, );
        return ;
    }
    public static final long FADE_DURATION = 200;
    @Override
    public int getNumberOfLayers() {
        return super.getNumberOfLayers();
    }
    private static final double LOG_2 = Math.log(2);
    private static final int TILE_DIM = 256;
        @Override
        public void onCompleted(Exception eBitmapInfo result) {
            invalidateSelf();
        }
    };
    @Override
    public int getIntrinsicWidth() {
        // first check if image was loaded
        if ( != null) {
            if (. != null)
                return ..;
            if (. != null)
                return ..getScaledWidth(.getDisplayMetrics().densityDpi);
        }
        if ( != null)
            return ..getWidth();
        // check eventual image size...
        if ( > 0)
            return ;
        // no image, but there was an error
        if ( != null) {
            Drawable error = tryGetErrorResource();
            if (error != null)
                return error.getIntrinsicWidth();
        }
        // check placeholder
        Drawable placeholder = tryGetPlaceholderResource();
        if (placeholder != null)
            return placeholder.getIntrinsicWidth();
        // we're SOL
        return -1;
    }
    @Override
    public int getIntrinsicHeight() {
        if ( != null) {
            if (. != null)
                return ..;
            if (. != null)
                return ..getScaledHeight(.getDisplayMetrics().densityDpi);
        }
        if ( != null)
            return ..getHeight();
        if ( > 0)
            return ;
        if ( != null) {
            Drawable error = tryGetErrorResource();
            if (error != null)
                return error.getIntrinsicHeight();
        }
        Drawable placeholder = tryGetPlaceholderResource();
        if (placeholder != null)
            return placeholder.getIntrinsicHeight();
        return -1;
    }
    @Override
    public void draw(Canvas canvas) {
        if ( == null) {
            // setup the placeholder if needed
            tryGetPlaceholderResource();
            // draw stuff
            super.draw(canvas);
            // see if we can fetch a bitmap
            if ( != null) {
                if (. == 0 && . == 0) {
                    if (canvas.getWidth() != 1)
                        . = canvas.getWidth();
                    if (canvas.getHeight() != 1)
                        . = canvas.getHeight();
                    // now that we have final dimensions, reattempt to find the image in the cache
                    .recomputeDecodeKey();
                    BitmapInfo found = ..get(.);
                    if (found != null) {
                        // found what we're looking for, but can't draw at this very moment,
                        // since we need to trigger a new measure.
                        .onCompleted(nullfound);
                        return;
                    }
                }
                // no image found fetch it.
                .register(.);
                // check to see if there's too many imageview loads
                // already in progress
                if (BitmapFetcher.shouldDeferImageView()) {
                    .defer();
                } else {
                    .execute();
                }
                // won't be needing THIS anymore
                 = null;
            }
            // well, can't do anything else here.
            return;
        }
        if (. != null) {
            drawDeepZoom(canvas);
            return;
        }
        if (. == 0)
            . = SystemClock.uptimeMillis();
        long destAlpha = 0xFF;
        if () {
            destAlpha = ((SystemClock.uptimeMillis() - .) << 8) / ;
            destAlpha = Math.min(destAlpha, 0xFF);
        }
        // remove self if not visible
        if (destAlpha == 255) {
            if ( != null) {
                 = null;
                setDrawableByLayerId(0, );
            }
        } else {
            tryGetPlaceholderResource();
            //invalidate to fade in
            invalidateSelf();
        }
        if (. != null) {
            super.draw(canvas);
            GifFrame frame = .getCurrentFrame();
            if (frame != null) {
                .setAlpha((intdestAlpha);
                canvas.drawBitmap(frame.imagenullgetBounds(), );
                .setAlpha(0xFF);
                invalidateSelf();
            }
            return;
        }
        if ( != null) {
            .setAlpha((intdestAlpha);
        } else {
            tryGetErrorResource();
            if ( != null)
                .setAlpha((intdestAlpha);
        }
        super.draw(canvas);
        if (true)
            return;
        // stolen from picasso
        canvas.save();
        canvas.rotate(45);
        .setColor(Color.WHITE);
        canvas.drawRect(0, -10, 7.5f, 10, );
        int sourceColor;
        switch () {
            case ..:
                sourceColor = Color.CYAN;
                break;
                sourceColor = Color.YELLOW;
                break;
            case ..:
                sourceColor = Color.GREEN;
                break;
            default:
                sourceColor = Color.RED;
                break;
        }
        .setColor(sourceColor);
        canvas.drawRect(0, -9, 6.5f, 9, );
        canvas.restore();
    }
    private void drawDeepZoom(Canvas canvas) {
        // zoom 0: entire image fits in a TILE_DIMxTILE_DIM square
        // draw base bitmap for empty tiles
        // figure out zoom level
        // figure out which tiles need rendering
        // draw stuff that needs drawing
        // missing tile? fetch it
        // use parent level tiles for tiles that do not exist
        // TODO: crossfading?
        Rect clip = canvas.getClipBounds();
        Rect bounds = getBounds();
        float zoom = (float)canvas.getWidth() / (float)clip.width();
        float zoomWidth = zoom * bounds.width();
        float zoomHeight = zoom * bounds.height();
        double wlevel = Math.log(zoomWidth / ) / ;
        double hlevel = Math.log(zoomHeight) / ;
        double maxLevel = Math.max(wlevelhlevel);
        int visibleLeft = Math.max(0, clip.left);
        int visibleRight = Math.min(bounds.width(), clip.right);
        int visibleTop = Math.max(0, clip.top);
        int visibleBottom = Math.min(bounds.height(), clip.bottom);
        int level = (int)Math.floor(maxLevel);
        level = Math.min(this.level);
        level = Math.max(level, 0);
        int levelTiles = 1 << level;
        int textureTileDim =  / levelTiles;
//            System.out.println("textureTileDim: " + textureTileDim);
//            System.out.println(info.key + " visible: " + new Rect(visibleLeft, visibleTop, visibleRight, visibleBottom));
        final boolean DEBUG_ZOOM = false;
        if (. != null) {
            canvas.drawBitmap(.nullgetBounds(), );
            if (DEBUG_ZOOM) {
                .setColor(Color.RED);
                .setAlpha(0x80);
                canvas.drawRect(getBounds(), );
                .setAlpha(0xFF);
            }
        }
        else {
            .setColor(Color.BLACK);
            canvas.drawRect(getBounds(), );
        }
        int sampleSize = 1;
        while (textureTileDim / sampleSize > )
            sampleSize <<= 1;
        for (int y = 0; y < levelTilesy++) {
            int top = textureTileDim * y;
            int bottom = textureTileDim * (y + 1);
            bottom = Math.min(bottombounds.bottom);
            // TODO: start at visible pos
            if (bottom < visibleTop)
                continue;
            if (top > visibleBottom)
                break;
            for (int x = 0; x < levelTilesx++) {
                int left = textureTileDim * x;
                int right = textureTileDim * (x + 1);
                right = Math.min(rightbounds.right);
                // TODO: start at visible pos
                if (right < visibleLeft)
                    continue;
                if (left > visibleRight)
                    break;
                Rect texRect = new Rect(lefttoprightbottom);
                // find, render/fetch
//                    System.out.println("rendering: " + texRect + " for: " + bounds);
                String tileKey = FileCache.toKeyString(.","level","x","y);
                BitmapInfo tile = ..get(tileKey);
                if (tile != null && tile.bitmap != null) {
                    // render it
//                        System.out.println("bitmap is: " + tile.bitmaps[0].getWidth() + "x" + tile.bitmaps[0].getHeight());
                    canvas.drawBitmap(tile.bitmapnulltexRect);
                    continue;
                }
                // TODO: cancellation of unnecessary regions when fast pan/zooming
                if (..tag(tileKey) == null) {
                    // fetch it
//                        System.out.println(info.key + ": fetching region: " + texRect + " sample size: " + sampleSize);
                    LoadBitmapRegion region = new LoadBitmapRegion(tileKey.texRectsampleSize);
                }
                ..add(tileKey);
                int parentLeft = 0;
                int parentTop = 0;
                int parentUp = 1;
                int parentLevel = level - parentUp;
                if (x % 2 == 1)
                    parentLeft++;
                if (y % 2 == 1)
                    parentTop++;
                int parentX = x >> 1;
                int parentY = y >> 1;
                while (parentLevel >= 0) {
                    tileKey = FileCache.toKeyString(.","parentLevel","parentX","parentY);
                    tile = ..get(tileKey);
                    if (tile != null && tile.bitmap != null)
                        break;
                    if (parentX % 2 == 1) {
                        parentLeft += 1 << parentUp;
                    }
                    if (parentY % 2 == 1) {
                        parentTop += 1 << parentUp;
                    }
                    parentLevel--;
                    parentUp++;
                    parentX >>= 1;
                    parentY >>= 1;
                }
                // well, i give up
                if (tile == null || tile.bitmap == null)
                    continue;
                int subLevelTiles = 1 << parentLevel;
                int subtileDim =  / subLevelTiles;
                int subSampleSize = 1;
                while (subtileDim / subSampleSize > )
                    subSampleSize <<= 1;
                int subTextureDim = subtileDim / subSampleSize;
//                    System.out.println(String.format("falling back for %s,%s,%s to %s,%s,%s: %s,%s (%s to %s)", x, y, level, parentX, parentY, parentLevel, parentLeft, parentTop, subTextureDim, subTextureDim >> parentUp));
                subTextureDim >>= parentUp;
                int sourceLeft = subTextureDim * parentLeft;
                int sourceTop = subTextureDim * parentTop;
                Rect sourceRect = new Rect(sourceLeftsourceTopsourceLeft + subTextureDimsourceTop + subTextureDim);
                canvas.drawBitmap(tile.bitmapsourceRecttexRect);
                if (DEBUG_ZOOM) {
                    .setColor(Color.RED);
                    .setAlpha(0x80);
                    canvas.drawRect(texRect);
                    .setAlpha(0xFF);
                }
            }
        }
    }
    @Override
    public void setAlpha(int alpha) {
       .setAlpha(alpha);
    }
    @Override
    public void setColorFilter(ColorFilter cf) {
        .setColorFilter(cf);
    }
    @Override
    public int getOpacity() {
        return ( == null || . == null || ..hasAlpha() || .getAlpha() < 255) ?
                PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
    }
    static IonDrawable getOrCreateIonDrawable(ImageView imageView) {
        Drawable current = imageView.getDrawable();
        IonDrawable ret;
        if (current == null || !(current instanceof IonDrawable))
            ret = new IonDrawable(imageView.getResources());
        else
            ret = (IonDrawable)current;
        // invalidate self doesn't seem to trigger the dimension check to be called by imageview.
        // are drawable dimensions supposed to be immutable?
        imageView.setImageDrawable(null);
        return ret;
    }
New to GrepCode? Check out our FAQ X