Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graphics.drawArc(), Graphics.fillArc() often fail on iOS, but never on Android #1878

Closed
SwingGuy1024 opened this issue Sep 1, 2016 · 7 comments
Assignees
Milestone

Comments

@SwingGuy1024
Copy link
Contributor

SwingGuy1024 commented Sep 1, 2016

I display some images that I created using Image.createImage(). In some cases, the images suddenly vanish from the screen, but only on iOS. The images are permanent on Android.

To reproduce, run the enclosed app on both Android and iOS. The app shows two images. The second one is taken from the bottom half of the first one. On Android, the images remain on the screen. On iOS, the images show up for about two to five seconds, then vanish. It turns out that they vanish when the app becomes active. They're only visible at the start because the startup image is visible. Further tests reveal that the images are the correct size, but are filled with transparent pixels.

I should say that, in my actual application, these images scale with the size of the screen, and are colored according to a user preference, so I can't get them from a resource.

(BTW Notice the change I made to the stop method. This is unrelated but worth mentioning.)

Here's the test case: This test case is improved from the original, because it shows three images, one of which doesn't work at all, one of which partly works, and one of which works completely.

import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Dialog;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import com.codename1.ui.Toolbar;

/**
 * This file was generated by <a href="https://www.codenameone.com/">Codename One</a> for the purpose
 * of building native mobile applications using Java.
 */
@SuppressWarnings("unused")
public class HalfImageBug {

  private Form current;

  public void init(Object context) {
    //noinspection HardcodedFileSeparator
    final Resources theme = UIManager.initFirstTheme("/theme");

    // Enable Toolbar on all Forms by default
    Toolbar.setGlobalToolbar(true);
  }

  public void start() {
    if (current != null) {
      current.show();
      return;
    }
    Form hi = new Form("Hi World", new BorderLayout());
    hi.addComponent(BorderLayout.CENTER, makeComponent());
    hi.show();
  }

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

    // This was originally if, but it should be while, in case there are multiple layers of dialogs.
    while (current instanceof Dialog) {
      ((Dialog) current).dispose();
      current = Display.getInstance().getCurrent();
    }
  }

  public void destroy() {
  }

  private Component makeComponent() {
    final Container container = new Container(new BoxLayout(BoxLayout.Y_AXIS));
    container.setScrollableY(true);
    final int color = 0x44ff00;
    Image fullIcon = createFullImage(color, 40, 30, true);
    //noinspection StringConcatenation
    container.add(new Label("Doesn't work: "));
    Label fullImage = new Label(fullIcon);
    container.add(fullImage);

    Image fullCopy = createFullImage(color, 40, 30, false);
    container.add(new Label("--- ---"));
    container.add(new Label("Partly works:"));
    container.add(new Label(fullCopy));

    container.add(new Label("---"));
    container.add(new Label("Works:"));
    container.add(new Label(createBubbleImage(40, color)));

    return container;
  }

  private Image createFullImage(int color, int verticalDiameter, int horizontalRadius, boolean mask) {

    // Make sure it's an even number. Otherwise the half image will have its right and left halves reversed!
    int diameter = (verticalDiameter / 2) * 2;
    final int iconWidth = 2 * horizontalRadius;
    int imageWidth = iconWidth + 2;
    int imageHt = diameter + 2;
    Image fullImage = Image.createImage(imageWidth, imageHt);
    Graphics g = fullImage.getGraphics();
    g.setAntiAliased(true);
    g.setColor(color);
    g.fillRect(0, 0, imageWidth, imageHt);
    g.setColor(darken(color, 25));
    g.fillArc(1, 1, iconWidth, diameter, 180, 360);
    g.setColor(0xbfbfbf);
    final int smallerHt = (9 * diameter) / 10;
    g.fillArc(0, 0, iconWidth, smallerHt, 180, 360);

    if (mask) {
      Image maskImage = Image.createImage(imageWidth, imageHt);
      g = maskImage.getGraphics();
      g.setAntiAliased(true);
      g.setColor(0);
      g.fillRect(0, 0, imageWidth, imageHt);
      g.setColor(0xFF);
      g.fillArc(1, 1, iconWidth, diameter, 180, 360);
      fullImage = fullImage.applyMask(maskImage.createMask());
    }
    return fullImage;
  }

  private Image createBubbleImage(int diameter, int bubbleRimColor) {
    int bubblePad = 3;
    final int width = diameter + (2 * bubblePad);

    // Create the bubble image
    //noinspection SuspiciousNameCombination
    Image bImage = Image.createImage(width, width);
    Graphics g = bImage.getGraphics();
    g.setColor(grayLighten(bubbleRimColor, 45));

    //noinspection SuspiciousNameCombination
    g.fillRect(0, 0, width, width);
    g.setColor(grayLighten(bubbleRimColor, 65));
    int bubbleLip = 2;
    int loc = bubbleLip + 1;
    int bubbleInteriorDiameter = diameter - (2 * bubbleLip);
    g.setAntiAliased(true);
    g.fillArc(loc, loc, bubbleInteriorDiameter, bubbleInteriorDiameter, 0, 360);

    //noinspection SuspiciousNameCombination
    Image maskImage = Image.createImage(width, width);
    g = maskImage.getGraphics();
    g.setColor(0);
    //noinspection SuspiciousNameCombination
    g.fillRect(0, 0, width, width);
    g.setColor(0xff);
    g.setAntiAliased(true);
    g.fillArc(1, 1, diameter, diameter, 0, 360);

    bImage = bImage.applyMask(maskImage.createMask());
    return bImage;
  }

  private static int darken(int color, int percent) {
    if ((percent > 100) || (percent < 0)) {
      throw new IllegalArgumentException("Percent out of range: " + percent);
    }
    int percentRemaining = 100 - percent;
    return (darkenPrimary((color & 0xFF0000) >> 16, percentRemaining) << 16)
        | (darkenPrimary((color & 0xFF00) >> 8, percentRemaining) << 8)
        | (darkenPrimary(color & 0xFF, percentRemaining));
  }

  private static int darkenPrimary(int primaryValue, int percentRemaining) {
    if ((primaryValue < 0) || (primaryValue > 255)) {
      throw new IllegalArgumentException("Primary value out of range (0-255): " + primaryValue);
    }

    return (primaryValue * percentRemaining) / 100;
  }

  private static int lightenPrimary(int primaryValue, int percentRemaining) {
    int delta = 255 - primaryValue;
    return 255 - ((delta * percentRemaining) / 100);
  }

  private static int grayLightenPrimary(int color, int percent) {
    return (lightenPrimary(color, 100 - percent) * percent) / 100;
  }

  private static int grayLighten(int color, int percent) {
    int percentRemaining = 100 - percent;
    return (grayLightenPrimary((color & 0xFF0000) >> 16, percentRemaining) << 16)
        | (grayLightenPrimary((color & 0xFF00) >> 8, percentRemaining) << 8)
        | (grayLightenPrimary(color & 0xFF, percentRemaining));
  }
}
@SwingGuy1024
Copy link
Contributor Author

I just figured out one piece of the puzzle, but the reason for the disappearance remains a mystery. I discovered that, when the images are visible, that's only in the application's start up screen. The images vanish when the startup screen goes away. (I discovered this when I added more images and made the screen scrollable. The images vanished as soon as the screen started scrolling. Which means when the app became operational.)

Why they're not visible remains a mystery, but my suspicions have changed. I previously suspected that something was getting garbage collected incorrectly, and now I suspect the drawing code simply fails to draw them in the first place.

@saeder
Copy link

saeder commented Sep 2, 2016

I've had a similiar thing with images on IOS. And I never figured out what
was wrong with my images.

SwingGuy1024 notifications@github.com schrieb am Fr., 2. Sep. 2016 um
01:39 Uhr:

I just figured out one piece of the puzzle, but the reason for the
disappearance remains a mystery. I discovered that, when the images are
visible, that's only in the application's start up screen. The images
vanish when the startup screen goes away. (I discovered this when I added
more images and made the screen scrollable. The images vanished as soon as
the screen started scrolling. Which means when the app became operational.)

Why they're not visible remains a mystery, but my suspicions have changed.
I previously suspected that something was getting garbage collected
incorrectly, and now I suspect the drawing code simply fails to draw them
in the first place.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#1878 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AJfAK0wBKsnmTSZ5gDQjQxhfgfmfBvanks5ql2IugaJpZM4Jyf-r
.

@SwingGuy1024
Copy link
Contributor Author

I just changed the test case to one with three similar images. One doesn't work at all, one partly works, and one works completely. This may help track down the problem.

@SwingGuy1024
Copy link
Contributor Author

SwingGuy1024 commented Sep 3, 2016

Here's my final test case, which narrows down the difference between success and failure to a single parameter. It turns out that if the Graphics.fillArc() method draws an arc with width == height, it works fine. But If it draws an arc where width > height, it doesn't draw anything. And if width < height, it draws incorrectly — it draws a circle with a diameter equal to the width, instead of an ellipse.

This code demonstrates that behavior. It calls the same method five times, with different pairs of parameters. Two have width > height, two have width < height, and one has width == height. These lines are marked with comments.

Here are two illustrations showing what it should draw (left) vs. what it draws (right). In each row, it shows first the raw image, then the mask to be applied, then the result after applying the mask.

photo 5 photo 4

Note that in the top two rows, the ellipses don't draw at all in the right image, and that in the bottom two rows, they draw as circles. The left image shows how they should look.

import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Dialog;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.Label;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.GridLayout;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import com.codename1.ui.Toolbar;

/**
 * This file was generated by <a href="https://www.codenameone.com/">Codename One</a> for the purpose
 * of building native mobile applications using Java.
 */
@SuppressWarnings("unused")
public class HalfImageBug {

  private Form current;

  public void init(Object context) {
    //noinspection HardcodedFileSeparator
    final Resources theme = UIManager.initFirstTheme("/theme");

    // Enable Toolbar on all Forms by default
    Toolbar.setGlobalToolbar(true);

  }

  public void start() {
    if (current != null) {
      current.show();
      return;
    }
    Form hi = new Form("Hi World", new BorderLayout());
    hi.addComponent(BorderLayout.CENTER, makeComponent());
    hi.show();
  }

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

    // This was originally if, but it should be while, in case there are multiple layers of dialogs.
    while (current instanceof Dialog) {
      ((Dialog) current).dispose();
      current = Display.getInstance().getCurrent();
    }
  }

  public void destroy() {
  }

  private Component makeComponent() {
    final GridLayout layout = new GridLayout(3);
    final Container container = new Container(layout);
    container.setScrollableY(true);
    final int color = 0x44ff00;
    addRow(container, createEllipseImage(color, 60, 36)); // If it width > height, it fails to draw.
    addRow(container, createEllipseImage(color, 37, 36)); // If it width > height, it fails to draw.
    addRow(container, createEllipseImage(color, 36, 36)); // If it width = height, it works.
    addRow(container, createEllipseImage(color, 35, 36)); // If it width < height, it draws a circle.
    addRow(container, createEllipseImage(color, 20, 36)); // If it width < height, it draws a circle.

    return container;
  }

  private void addRow(Container container, ImageCombo combo) {
    Image image = combo.getImage();
    //noinspection StringConcatenation
    String title = "(" + image.getWidth() + ", " + image.getHeight() + "):";
    container.add(new Label(title));
    container.add(new Label(""));
    container.add(new Label(""));
    container.add(combo.getImage());
    container.add(combo.getMask());
    container.add(combo.getMaskedImage());
  }

  private ImageCombo createEllipseImage(int color, int imageWidth, int imageHt) {
    int iconHeight = imageHt - 2;
    final int iconWidth = imageWidth - 2;

    Image fullImage = Image.createImage(imageWidth, imageHt);
    Graphics g = fullImage.getGraphics();
    g.setAntiAliased(true);
    g.setColor(color);
    g.fillRect(0, 0, imageWidth, imageHt);
    g.darkerColor(40);
    g.fillArc(1, 1, iconWidth, iconHeight, 0, 360); // fails when iconWidth != iconHeight
    Image maskImage = makeMask(fullImage, iconWidth, iconHeight);

    return new ImageCombo(fullImage, maskImage);
  }

  private Image makeMask(Image original, int maskWidth, int maskHeight) {
    int imageWidth = original.getWidth();
    int imageHt = original.getHeight();
    Image maskImage = Image.createImage(imageWidth, imageHt);
    Graphics g = maskImage.getGraphics();
    g.setColor(0);
    g.fillRect(0, 0, imageWidth, imageHt);
    g.setColor(0xFF);
    g.setAntiAliased(true);
    g.fillArc(1, 1, maskWidth, maskHeight, 0, 360); // fails when maskWidth != maskHeight
    return maskImage;
  }

  private class ImageCombo {
    private final Image image;
    private final Image mask;

    ImageCombo(Image image, Image mask) {
      this.image = image;
      this.mask = mask;
    }

    Image getImage() { return image; }

    Image getMask() { return mask; }

    Image getMaskedImage() { return image.applyMask(mask.createMask()); }
  }
}

@SwingGuy1024
Copy link
Contributor Author

I just did a test where I used drawArc() instead of fillArc(). With drawArc, when width < height, it still draw a circle, but when width > arc, it draws just a straight line across the middle of the ellipse.

@SwingGuy1024 SwingGuy1024 changed the title Images vanish when running on iOS, but not on Android Graphics.drawArc(), Graphics.fillArc() often fail on iOS, but never on Android Sep 7, 2016
@SwingGuy1024
Copy link
Contributor Author

I just changed the title of this issue for clarity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants