Search code examples
codenameone

Codename One Image resizing performance


I think that resizing an image to a given dimension in pixels to show it at the wanted size is more expensive than using that image as background of a Label that has the wanted preferred size. Could you confirm that it's true? And, if so, why is it true?

I mean that using a class like the following is better (in terms of performance) than resizing the contained image. Do you think that a faster approach is possible? I need to display a lot of photos at a fixed size, but my app is not as faster as the native gallery app of the smartphone.

/**
 * Button useful to show the given Image at the given size, without the need to
 * resize it.
 *
 * @author Francesco Galgani
 */
public class FixedSizeButton extends Button {

    private final int imageWidth;
    private final int imageHeight;
    private Image image;

    /**
     * Creates a Button displaying an Image at the given fixed size; at least
     * one of imageWidth or imageHeight must be specified.
     *
     * @param image
     * @param imageWidth in pixels, can be -1 to automatically resize
     * maintaining the aspect ratio
     * @param imageHeight in pixels, can be -1 to automatically resize
     * maintaining the aspect ratio
     */
    public FixedSizeButton(Image image, int imageWidth, int imageHeight) {
        this(image, imageWidth, imageHeight, null);
    }

    /**
     * Creates a Button displaying an Image at the given fixed size; at least
     * one of imageWidth or imageHeight must be specified.
     *
     * @param image
     * @param imageWidth in pixels, can be -1 to automatically resize
     * maintaining the aspect ratio
     * @param imageHeight in pixels, can be -1 to automatically resize
     * maintaining the aspect ratio
     * @param uiid
     */
    public FixedSizeButton(Image image, int imageWidth, int imageHeight, String uiid) {
        this(image, imageWidth, imageHeight, false, uiid);
    }

    /**
     * Creates a Button displaying an Image at the given fixed size; at least
     * one of imageWidth or imageHeight must be specified.
     *
     * @param image
     * @param imageWidth in pixels, can be -1 to automatically resize
     * maintaining the aspect ratio
     * @param imageHeight in pixels, can be -1 to automatically resize
     * maintaining the aspect ratio
     * @param scaledSmallerRatio force the image to maintain the aspect ratio
     * within the given dimension (it requires that both imageWidth and
     * imageHeight are specified)
     * @param uiid
     */
    public FixedSizeButton(Image image, int imageWidth, int imageHeight, boolean scaledSmallerRatio, String uiid) {
        if (image == null) {
            throw new IllegalArgumentException("image cannot be null");
        }
        if (imageWidth <= 0 && imageHeight <= 0) {
            throw new IllegalArgumentException("invalid imageWidth and imageHeight");
        }
        this.image = image;
        setShowEvenIfBlank(true);
        if (uiid != null) {
            super.setUIID(uiid);
        }
        if (imageWidth < 1) {
            imageWidth = image.getWidth() * imageHeight / image.getHeight();
        } else if (imageHeight < 1) {
            imageHeight = image.getHeight() * imageWidth / image.getWidth();
        }
        if (scaledSmallerRatio) {
            float hRatio = ((float) imageHeight) / ((float) image.getHeight());
            float wRatio = ((float) imageWidth) / ((float) image.getWidth());
            if (hRatio < wRatio) {
                imageWidth = (int) (image.getWidth() * hRatio);
            } else {
                imageHeight = (int) (image.getHeight() * wRatio);
            }
        }
        this.getAllStyles().setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FIT);
        this.getAllStyles().setBgImage(image);
        this.imageWidth = imageWidth;
        this.imageHeight = imageHeight;
    }

    @Override
    public Dimension calcPreferredSize() {
        int width = imageWidth + this.getStyle().getPaddingLeftNoRTL() + this.getStyle().getPaddingRightNoRTL();
        int height = imageHeight + this.getStyle().getPaddingTop() + this.getStyle().getPaddingBottom();
        return new Dimension(width, height);
    }

    /**
     * Returns the background image
     *
     * @return the bg image
     */
    @Override
    public Image getIcon() {
        return image;
    }

    @Override
    public void setText(String text) {
        throw new IllegalStateException("Not supported");
    }

    @Override
    public void setUIID(String id) {
        super.setUIID(id);
        this.getAllStyles().setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FIT);
        this.getAllStyles().setBgImage(image);
    }

}

Solution

  • The performance footprint/overhead varies. When you do Image.scaled (which I assume is what you meant by resizing...), the image might be traversed pixel by pixel and shrunk to a new smaller byte array. Then it might be re-encoded as PNG or JPEG. All of those are expensive tasks.

    Note that I used the word "might" as iOS doesn't always do it and uses hardware scaling in some cases...

    When you use drawing the image is loaded as a texture into the GPU which draws/scales the image. This has zero overhead and is pretty darn fast.The big drawback here is RAM which is taken up both in regular heap and GPU memory. So it's a trade-off you need to make.