codenameone / CodenameOne

Cross-platform framework for building truly native mobile apps with Java or Kotlin. Write Once Run Anywhere support for iOS, Android, Desktop & Web.
https://www.codenameone.com/
Other
1.71k stars 409 forks source link

Shape clipping not working on Image.createImage(width, height).getGraphics() #2027

Closed saeder closed 7 years ago

saeder commented 7 years ago

Shape clipping on image.graphics works in the simulator but not on IOS.

This code demonstrates it by displaying a draggable lens with has a circle shape in the simulator - because shape clipping works there - and a square shape on IOS:

public class FormImageShapeClip extends Form {
    private class DraggableLens extends Component {
        public DraggableLens() {
            setWidth(getPreferredW());
            setHeight(getPreferredH());
        }

        @Override
        public Container getParent() {
            return FormImageShapeClip.this;
        }
        @Override
        protected Dimension calcPreferredSize() {
            Display display = Display.getInstance();
            int size = display.getDisplayWidth() / 5;
            return new Dimension(size, size);
        }
        @Override
        protected boolean isDragAndDropOperation(int x, int y) {
            return getBounds().contains(x, y);
        }
        @Override
        public void paint(Graphics aGraphics) {
            if (null != imageWallpaperLens) {
                if (aGraphics.isAntiAliasingSupported()) {
                    aGraphics.setAntiAliased(true);
                }
                GeneralPath generalPath = GeneralPath.createFromPool();
                int absoluteX = getAbsoluteX();
                int absoluteY = getAbsoluteY();
                generalPath.moveTo(absoluteX, absoluteY);
                int width = getWidth();
                int height = getHeight();
                generalPath.arc(absoluteX, absoluteY, width, height, 0, 2 * Math.PI);
                generalPath.closePath();
                try {
//                  { // Doesn't work with IOSPort
//                      aGraphics.pushClip();
//                      int[] clip = aGraphics.getClip();
//                      aGraphics.setClip(generalPath);
//                      aGraphics.clipRect(clip[0], clip[1], clip[2], clip[3]);
//                      aGraphics.drawImage(imageBlurred, 0, 0);
//                      int alpha = aGraphics.getAlpha();
//                      aGraphics.setAlpha(0x80);
//                      aGraphics.setColor(0xffffff);
//                      aGraphics.fillShape(generalPath);
//                      aGraphics.setAlpha(alpha);
//                      aGraphics.popClip();
//                  
//                  }
                    { // Workaround - Clipping via image graphics
                        int[] clip = aGraphics.getClip();
                        Rectangle rectangleClipping = new Rectangle(clip[0], clip[1], clip[2], clip[3]);
                        Rectangle rectangleComponentAbsolute = new Rectangle(
                                absoluteX, 
                                absoluteY, 
                                width, 
                                height);
                        Rectangle rectangleIntersection = rectangleClipping.intersection(rectangleComponentAbsolute);
                        Image imageClippingSize = Image.createImage(
                                rectangleIntersection.getWidth(), 
                                rectangleIntersection.getHeight(), 
                                0x00ffffff); // Transparent image
                        Graphics graphicsImageClippingSize = imageClippingSize.getGraphics();
                        graphicsImageClippingSize.translate(
                                -rectangleIntersection.getX(), 
                                -rectangleIntersection.getY());
                        graphicsImageClippingSize.setClip(generalPath); // Image now clipped by circle shape
                        graphicsImageClippingSize.drawImage(imageWallpaperLens, 0, 0);
                        int shiftX = absoluteX < rectangleIntersection.getX() ? width - rectangleIntersection.getWidth() : 0;
                        int shiftY = absoluteY < rectangleIntersection.getY() ? height - rectangleIntersection.getHeight() : 0;
                        aGraphics.drawImage(
                                imageClippingSize, 
                                getX() + shiftX, 
                                getY() + shiftY);
                    }
                } finally {
                    GeneralPath.recycle(generalPath);
                }
            }
        }
    }

    Image imageWallpaperLens = null;
    private DraggableLens draggableLens = null;
    private Runnable runnableOnShowCompleted;
    private Point pointPressedOffset;

    public FormImageShapeClip() {
        super("FormImageShapeClip");
        setScrollable(false);
        setGlassPane((aGraphics, aRectangle) -> {
            if (null != draggableLens) {
                draggableLens.paint(aGraphics);
            }
        });
        runnableOnShowCompleted = () -> {
            Container contentPane = getContentPane();
            draggableLens = new DraggableLens();
            draggableLens.setX(contentPane.getAbsoluteX());
            draggableLens.setY(contentPane.getAbsoluteY());
        };
        addPointerPressedListener((aActionEvent) -> {
            pointPressedOffset = draggableLens.isDragAndDropOperation(aActionEvent.getX(), aActionEvent.getY()) 
                    ? new Point(aActionEvent.getX() - draggableLens.getX(), aActionEvent.getY() - draggableLens.getY()) 
                    : null;
        });
        addPointerReleasedListener((e) -> pointPressedOffset = null);
        addPointerDraggedListener((aActionEvent) -> {
            if (null != pointPressedOffset) {
                draggableLens.setX(aActionEvent.getX() - pointPressedOffset.getX());
                draggableLens.setY(aActionEvent.getY() - pointPressedOffset.getY());
            }
        });
    }

    private Painter createPainter(int aColorBackground, int aColorA, int aColorB) {
        return (aGraphics, aRectangle) -> {
            if (aGraphics.isAntiAliasingSupported()) {
                aGraphics.setAntiAliased(true);
            }
            aGraphics.setColor(aColorBackground);
            aGraphics.fillRect(aRectangle.getX(), aRectangle.getY(), aRectangle.getWidth(), aRectangle.getHeight());
            int offset = aRectangle.getHeight() / 4;
            int step = aRectangle.getWidth() * 5 / 100;
            aGraphics.setColor(aColorB);
            for (int x = step; x < (aRectangle.getWidth() + offset); x += step) {
                aGraphics.drawLine(x - offset, aRectangle.getHeight(), x, 0);
            }
            aGraphics.setColor(aColorA);
            for (int x = step; x < (aRectangle.getWidth() + offset); x += step) {
                aGraphics.drawLine(x, aRectangle.getHeight(), x - offset, 0);
            }
        };
    }

    @Override
    protected void onShowCompleted() {
        runnableOnShowCompleted.run();
        { // Wallpaper Lens
            imageWallpaperLens = Image.createImage(getWidth(), getHeight());
            Graphics graphicsImageWallpaper = imageWallpaperLens.getGraphics();
            getAllStyles().setBgPainter(createPainter(0x707070, 0x00ff00, 0x0000ff));
            paint(graphicsImageWallpaper);
        }
        getAllStyles().setBgPainter(createPainter(0xc0c0c0, 0xffffff, 0x000000));
    }
}
saeder commented 7 years ago

In the simulator it looks like ... imageshapeclipsimulator

saeder commented 7 years ago

On an iPhone 5S it looks like ... fullsizerender

saeder commented 7 years ago

By the way the lines drawn directly to the IOS device are not antialiased and the lines drawn to the image are antialiased. I raised an issue #2028 - Antialiasing not working as expected

codenameone commented 7 years ago

Actually looking at this again this should work fine isShapeClipSupported should return false on that Graphics object of the mutable image as intended.

Doing shape clipping on images is probably bad performance especially in platforms where Display.areMutableImagesSlow() returns true as happens to be the case on iOS.

saeder commented 7 years ago

Actually Image.createImage(128, 128, 0x00ffffff).getGraphics().isShapeClipSupported() returns `true´ on IOS - at least that should be fixed then, don't You agree?

codenameone commented 7 years ago

Sorry my bad, I read the code incorrectly. Just committed a fix and it should work right this Friday

saeder commented 7 years ago

I tested it this morning - shapeClipSupported on Image.getGraphics returns true, but shape clipping does not work