Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2d56b273 authored by Xavier Ducrohet's avatar Xavier Ducrohet
Browse files

Layoutlib: use default nine patch classes.

Move away from using our own implementation of NinePatchDrawable.
Now use native delegate for a few methods of NinePatch.

The byte[] used to describe the 9-patch chunk is entirely controlled
by the delegate. Therefore, while the default version (JNI) use the
array as a representation of a C struct, this version uses the array
as a serialized version of NinePatchChunk.

A cache mechanism using SoftReferences allows us to not deserialize
the array every time rendering needs to access the chunk itself.

The Bridge-level cache mechanism for bitmaps and nine-patches as
changed. Since the new nine-patches doesn't hold the bitmap
data anymore (it's stored in a normal Android bitmap which
is cached itself through the cache), then the nine-patch cache
has been changed to only contain the nine patch chunk.

Also initialize the canvas with the display metrics to prepare
for correct scaling when density of the assets don't match the target
density.

Still to come: actual density support in the 9-patch drawing code.

Change-Id: Ibefcccf4432e1986e8436e0c41a0107741593536
parent b43d7589
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -456,7 +456,11 @@ public class BitmapFactory {
            // into is.read(...) This number is not related to the value passed
            // to mark(...) above.
            try {
                bm = Bitmap_Delegate.createBitmap(is, Density.MEDIUM);
                Density density = Density.MEDIUM;
                if (opts != null) {
                    density = Density.getEnum(opts.inDensity);
                }
                bm = Bitmap_Delegate.createBitmap(is, true, density);
            } catch (IOException e) {
                return null;
            }
+34 −11
Original line number Diff line number Diff line
@@ -75,32 +75,56 @@ public class Bitmap_Delegate {

    /**
     * Creates and returns a {@link Bitmap} initialized with the given file content.
     *
     * @param input the file from which to read the bitmap content
     * @param isMutable whether the bitmap is mutable
     * @param density the density associated with the bitmap
     *
     * @see Bitmap#isMutable()
     * @see Bitmap#getDensity()
     */
    public static Bitmap createBitmap(File input, Density density) throws IOException {
    public static Bitmap createBitmap(File input, boolean isMutable, Density density)
            throws IOException {
        // create a delegate with the content of the file.
        Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input));

        return createBitmap(delegate, density.getValue());
        return createBitmap(delegate, isMutable, density.getValue());
    }

    /**
     * Creates and returns a {@link Bitmap} initialized with the given stream content.
     *
     * @param input the stream from which to read the bitmap content
     * @param isMutable whether the bitmap is mutable
     * @param density the density associated with the bitmap
     *
     * @see Bitmap#isMutable()
     * @see Bitmap#getDensity()
     */
    public static Bitmap createBitmap(InputStream input, Density density) throws IOException {
    public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density)
            throws IOException {
        // create a delegate with the content of the stream.
        Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input));

        return createBitmap(delegate, density.getValue());
        return createBitmap(delegate, isMutable, density.getValue());
    }

    /**
     * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
     *
     * @param image the bitmap content
     * @param isMutable whether the bitmap is mutable
     * @param density the density associated with the bitmap
     *
     * @see Bitmap#isMutable()
     * @see Bitmap#getDensity()
     */
    public static Bitmap createBitmap(BufferedImage image, Density density) throws IOException {
    public static Bitmap createBitmap(BufferedImage image, boolean isMutable, Density density)
            throws IOException {
        // create a delegate with the given image.
        Bitmap_Delegate delegate = new Bitmap_Delegate(image);

        return createBitmap(delegate, density.getValue());
        return createBitmap(delegate, isMutable, density.getValue());
    }

    /**
@@ -153,7 +177,7 @@ public class Bitmap_Delegate {
        // create a delegate with the content of the stream.
        Bitmap_Delegate delegate = new Bitmap_Delegate(image);

        return createBitmap(delegate, Bitmap.getDefaultDensity());
        return createBitmap(delegate, mutable, Bitmap.getDefaultDensity());
    }

    /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) {
@@ -166,8 +190,7 @@ public class Bitmap_Delegate {
    }

    /*package*/ static void nativeRecycle(int nativeBitmap) {
        // FIXME implement native delegate
        throw new UnsupportedOperationException("Native delegate needed for Bitmap");
        sManager.removeDelegate(nativeBitmap);
    }

    /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality,
@@ -336,11 +359,11 @@ public class Bitmap_Delegate {
        mImage = image;
    }

    private static Bitmap createBitmap(Bitmap_Delegate delegate, int density) {
    private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) {
        // get its native_int
        int nativeInt = sManager.addDelegate(delegate);

        // and create/return a new Bitmap with it
        return new Bitmap(nativeInt, true /*isMutable*/, null /*ninePatchChunk*/, density);
        return new Bitmap(nativeInt, isMutable, null /*ninePatchChunk*/, density);
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -960,7 +960,7 @@ public class Canvas_Delegate {
     * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
     * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
     */
    private Graphics2D getCustomGraphics(Paint_Delegate paint) {
    /*package*/ Graphics2D getCustomGraphics(Paint_Delegate paint) {
        // make new one
        Graphics2D g = getGraphics2d();
        g = (Graphics2D)g.create();
+225 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 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.
 */

package android.graphics;

import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.ninepatch.NinePatchChunk;

import android.graphics.drawable.NinePatchDrawable;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;

/**
 * Delegate implementing the native methods of android.graphics.NinePatch
 *
 * Through the layoutlib_create tool, the original native methods of NinePatch have been replaced
 * by calls to methods of the same name in this delegate class.
 *
 * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
 * around to map int to instance of the delegate.
 *
 */
public class NinePatch_Delegate {

    /**
     * Cache map for {@link NinePatchChunk}.
     * When the chunks are created they are serialized into a byte[], and both are put
     * in the cache, using a {@link SoftReference} for the chunk. The default Java classes
     * for {@link NinePatch} and {@link NinePatchDrawable} only reference to the byte[] data, and
     * provide this for drawing.
     * Using the cache map allows us to not have to deserialize the byte[] back into a
     * {@link NinePatchChunk} every time a rendering is done.
     */
    private final static Map<byte[], SoftReference<NinePatchChunk>> sChunkCache =
        new HashMap<byte[], SoftReference<NinePatchChunk>>();

    // ---- Public Helper methods ----

    /**
     * Serializes the given chunk.
     *
     * @return the serialized data for the chunk.
     */
    public static byte[] serialize(NinePatchChunk chunk) {
        // serialize the chunk to get a byte[]
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(baos);
            oos.writeObject(chunk);
        } catch (IOException e) {
            //FIXME log this.
            return null;
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                }
            }
        }

        // get the array and add it to the cache
        byte[] array = baos.toByteArray();
        sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk));
        return array;
    }

    // ---- native methods ----

    /*package*/ static boolean isNinePatchChunk(byte[] chunk) {
        NinePatchChunk chunkObject = getChunk(chunk);
        if (chunkObject != null) {
            return true;
        }

        return false;
    }

    /*package*/ static void validateNinePatchChunk(int bitmap, byte[] chunk) {
        // the default JNI implementation only checks that the byte[] has the same
        // size as the C struct it represent. Since we cannot do the same check (serialization
        // will return different size depending on content), we do nothing.
    }

    /*package*/ static void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance,
            byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) {
        draw(canvas_instance,
                (int) loc.left, (int) loc.top, (int) loc.width(), (int) loc.height(),
                bitmap_instance, c, paint_instance_or_null,
                destDensity, srcDensity);
    }

    /*package*/ static void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance,
            byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) {
        draw(canvas_instance,
                loc.left, loc.top, loc.width(), loc.height(),
                bitmap_instance, c, paint_instance_or_null,
                destDensity, srcDensity);
    }

   private static void draw(int canvas_instance,
           int left, int top, int right, int bottom,
           int bitmap_instance, byte[] c, int paint_instance_or_null,
           int destDensity, int srcDensity) {
       // get the delegate from the native int.
       Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
       if (bitmap_delegate == null) {
           assert false;
           return;
       }

       if (c == null) {
           // not a 9-patch?
           BufferedImage image = bitmap_delegate.getImage();
           Canvas_Delegate.native_drawBitmap(canvas_instance, bitmap_instance,
                   new Rect(0, 0, image.getWidth(), image.getHeight()),
                   new Rect(left, top, right, bottom),
                   paint_instance_or_null, destDensity, srcDensity);
           return;
       }

       NinePatchChunk chunkObject = getChunk(c);
       assert chunkObject != null;
       if (chunkObject == null) {
           return;
       }

       Canvas_Delegate canvas_delegate = Canvas_Delegate.getDelegate(canvas_instance);
       if (canvas_delegate == null) {
           assert false;
           return;
       }

       // this one can be null
       Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null);

       Graphics2D graphics;
       if (paint_delegate != null) {
           graphics = canvas_delegate.getCustomGraphics(paint_delegate);
       } else {
           graphics = canvas_delegate.getGraphics2d();
       }

       try {
           chunkObject.draw(bitmap_delegate.getImage(), graphics,
                   left, top, right - left, bottom - top);
       } finally {
           if (paint_delegate != null) {
               graphics.dispose();
           }
       }

    }

    /*package*/ static int nativeGetTransparentRegion(int bitmap, byte[] chunk, Rect location) {
        return 0;
    }

    // ---- Private Helper methods ----

    /**
     * Returns a {@link NinePatchChunk} object for the given serialized representation.
     *
     * If the chunk is present in the cache then the object from the cache is returned, otherwise
     * the array is deserialized into a {@link NinePatchChunk} object.
     *
     * @param array the serialized representation of the chunk.
     * @return the NinePatchChunk or null if deserialization failed.
     */
    private static NinePatchChunk getChunk(byte[] array) {
        SoftReference<NinePatchChunk> chunkRef = sChunkCache.get(array);
        NinePatchChunk chunk = chunkRef.get();
        if (chunk == null) {
            ByteArrayInputStream bais = new ByteArrayInputStream(array);
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(bais);
                chunk = (NinePatchChunk) ois.readObject();

                // put back the chunk in the cache
                if (chunk != null) {
                    sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk));
                }
            } catch (IOException e) {
                // FIXME: log this
                return null;
            } catch (ClassNotFoundException e) {
                // FIXME: log this
                return null;
            } finally {
                if (ois != null) {
                    try {
                        ois.close();
                    } catch (IOException e) {
                    }
                }
            }
        }

        return chunk;
    }
}
+16 −16
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@ import com.android.layoutlib.api.SceneResult;
import com.android.layoutlib.bridge.android.BridgeAssetManager;
import com.android.layoutlib.bridge.impl.FontLoader;
import com.android.layoutlib.bridge.impl.LayoutSceneImpl;
import com.android.ninepatch.NinePatch;
import com.android.ninepatch.NinePatchChunk;
import com.android.tools.layoutlib.create.MethodAdapter;
import com.android.tools.layoutlib.create.OverrideMethod;

@@ -73,13 +73,13 @@ public final class Bridge extends LayoutBridge {

    private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
        new HashMap<Object, Map<String, SoftReference<Bitmap>>>();
    private final static Map<Object, Map<String, SoftReference<NinePatch>>> sProject9PatchCache =
        new HashMap<Object, Map<String, SoftReference<NinePatch>>>();
    private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache =
        new HashMap<Object, Map<String, SoftReference<NinePatchChunk>>>();

    private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache =
        new HashMap<String, SoftReference<Bitmap>>();
    private final static Map<String, SoftReference<NinePatch>> sFramework9PatchCache =
        new HashMap<String, SoftReference<NinePatch>>();
    private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
        new HashMap<String, SoftReference<NinePatchChunk>>();

    private static Map<String, Map<String, Integer>> sEnumValueMap;

@@ -252,23 +252,23 @@ public final class Bridge extends LayoutBridge {
    }

    /**
     * Sets a 9 patch in a project cache or in the framework cache.
     * Sets a 9 patch chunk in a project cache or in the framework cache.
     * @param value the path of the 9 patch
     * @param ninePatch the 9 patch object
     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
     */
    public static void setCached9Patch(String value, NinePatch ninePatch, Object projectKey) {
    public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
        if (projectKey != null) {
            Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey);
            Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);

            if (map == null) {
                map = new HashMap<String, SoftReference<NinePatch>>();
                map = new HashMap<String, SoftReference<NinePatchChunk>>();
                sProject9PatchCache.put(projectKey, map);
            }

            map.put(value, new SoftReference<NinePatch>(ninePatch));
            map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
        } else {
            sFramework9PatchCache.put(value, new SoftReference<NinePatch>(ninePatch));
            sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));
        }
    }

@@ -436,24 +436,24 @@ public final class Bridge extends LayoutBridge {
    }

    /**
     * Returns the 9 patch for a specific path, from a specific project cache, or from the
     * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
     * framework cache.
     * @param value the path of the 9 patch
     * @param projectKey the key of the project, or null to query the framework cache.
     * @return the cached 9 patch or null if not found.
     */
    public static NinePatch getCached9Patch(String value, Object projectKey) {
    public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
        if (projectKey != null) {
            Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey);
            Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);

            if (map != null) {
                SoftReference<NinePatch> ref = map.get(value);
                SoftReference<NinePatchChunk> ref = map.get(value);
                if (ref != null) {
                    return ref.get();
                }
            }
        } else {
            SoftReference<NinePatch> ref = sFramework9PatchCache.get(value);
            SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
            if (ref != null) {
                return ref.get();
            }
Loading