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.72k stars 408 forks source link

IOS polygon drawing broken (worse than before) #3302

Open ddyer0 opened 4 years ago

ddyer0 commented 4 years ago

I have several open issues about drawing primitives on IOS. This reports that the situation has become worse. I haven't determined if you tried to fix the old complaints or are just the unlucky victim of bit decay. Here is a rendering from an android device, which is correct.
image

Here is a rendering from the identical sources on IOS. Note that many of the curved lines and filled polygons are rendered incorrectly. image

ddyer0 commented 4 years ago

The other issue that contains a simple test case related to this problem is #3037, but as I said in the initial description, the code that generates this picture worked as of a few months ago, and doesn't now.

ddyer0 commented 4 years ago

More information; I've determined that the "worse than before" version is a result of directly drawing in system windows, using the graphics objects provided by codename1. The "less broken" behavior reported in #3037 occurs when drawing to an offscreen bitmap and then transferring that bitmap to the real window.

ddyer0 commented 4 years ago

Here is a consolitdated test program that shows up a number of problems with affine transforms across a variety of platforms. If you run the test program, you should see a grid of scaled triangles inscribed in squares, and a slowly rotating triangle inscribed in a square at the center of the window. If you tap on the window, the test switches between drawing on an image buffer and drawing directly to the paint graphics context. So, for { simulator, android, ios } x { buffered, unbuffered } there are a total of 6 interesting "pictures" generated by the test program. The correct picture looks like this, and is produced ONLY by the simulator drawing to an offscreen buffer. The other 5 cases are incorrect.

Here's the correct picture. image

Here's the test program ''''java

package com.boardspace.dtest;

// issue #3302 // affine transforms in general // // this test program illustrates a number of problems with affine transformations // on the simulator, android and ios // // tap on the window to switch between buffered and direct drawing // // import com.codename1.ui.Display; import com.codename1.ui.Form; import com.codename1.ui.Label; import com.codename1.ui.Stroke; import com.codename1.ui.events.ActionEvent; import com.codename1.ui.events.ActionListener; import com.codename1.ui.geom.GeneralPath; import com.codename1.ui.geom.Rectangle; import com.codename1.ui.plaf.UIManager; import com.codename1.ui.util.Resources; import com.codename1.ui.Graphics; import com.codename1.ui.Image; import com.codename1.ui.layouts.BorderLayout;

class Polygon { GeneralPath path = new GeneralPath(); public Polygon() { } public void addPoint(int x,int y) { if(path.getCurrentPoint()==null) { path.moveTo(x,y); } else { path.lineTo(x,y); } } public void fill(Graphics g) { g.fillShape(path); } public void frame(Graphics g) { Stroke stroke = new Stroke( 1, Stroke.CAP_BUTT, Stroke.JOIN_ROUND, 1f );

    // Draw the shape
    g.drawShape(path, stroke);

}
public Rectangle getBounds() {
    return(path.getBounds());
}
public boolean contains(int i, int j) { return(path.getBounds().contains(i,j)); }

}

@SuppressWarnings("rawtypes") class DLabel extends Label implements ActionListener { int paints=0; Polygon p; boolean buffer = true;

public DLabel(String string) { super(string); addPointerPressedListener(this); } public void testFrame(Graphics g,int w,int h,String message) {

g.setColor(0x8f8f9f7f);
g.fillRect(0,0,w,h);
g.setColor(0);
g.drawString("Paint "+paints,10,30);
g.drawString(message, 10, 60);

int cx = w/2;
int cy = h/2;
int x0 = w/2-20;
int y0 = h/2-20;
g.rotateRadians(((float)(Math.PI+paints/100.0)),cx,cy);

g.setColor(0xffffff);
g.drawRect(x0-20,y0-20, 40, 20);
g.setColor(0xff0000);
g.fillRect(cx-10, cy-10, 20, 20);
g.setColor(0);
g.translate(x0,y0);
p.fill(g);          // triangle should be inscribed in the rectangle    
g.setColor(0xff00);
p.frame(g);
g.translate(-x0,-y0);

g.rotateRadians(-((float)(Math.PI+paints/100.0)),cx,cy);

// draw a grid of larger triangles scaled
for(int x=1;x<5;x++)
{ for(int y=1;y<5;y++)
    {   int xp = x*30;
        int yp = y*30;
        g.translate(xp,yp);
        g.scale(x,y);

        g.setColor(0xffffff);
        g.drawRect(-20,-20,40,20);
        g.setColor(0);
        p.fill(g);          // triangle should be inscribed in the rectangle    
        g.setColor(0xff00);
        p.frame(g);

        g.scale(1.0f/x,1.0f/y);
        g.translate(-xp, -yp);
    }
}

} public void paint(Graphics realG) {

    paints++;
    p = new Polygon();
    p.addPoint(-20, 0);
    p.addPoint(0, -20);
    p.addPoint(20, 0);
    p.addPoint(-20,0);

    int w = getWidth();
    int h = getHeight();
    if(buffer)
    {
    Image offscreen = Image.createImage(w,h);
    Graphics g = offscreen.getGraphics();
    g.setFont(realG.getFont());         // this shouldn't be necessary, but the default font is inappropriate
    testFrame(g,w,h,"draw to buffer");
    realG.drawImage(offscreen,0,0);
    }
    else
    {
        testFrame(realG,w,h,"draw to screen");
    }       

}
public void actionPerformed(ActionEvent evt) {
    buffer = !buffer;
    paints = 0;
}}    

@SuppressWarnings("rawtypes") public class Dtest implements ActionListener {

private Form current; @SuppressWarnings("unused") private Resources theme;

public void init(Object context) { theme = UIManager.initFirstTheme("/theme"); // Pro only feature, uncomment if you have a pro subscription // Log.bindCrashProtection(true); }

public void start() { if(current != null){ current.show(); } Form hi = new Form("Hi >>0 World"); current = hi; hi.setLayout(new BorderLayout()); hi.addComponent(BorderLayout.CENTER, new DLabel("Hi Overlay World 2")); hi.show(); Runnable rr = new Runnable (){ public void run() { System.out.println("running"); while(true) { hi.repaint(); try { Thread.sleep(100); }

      catch (InterruptedException e) {};
    }}};
new Thread(rr).start();

} public void stop() { current = Display.getInstance().getCurrent(); }

public void destroy() { } @Override public void actionPerformed(ActionEvent evt) { // TODO Auto-generated method stub

} } ''''