Графический код рисования генерирует пустые изображения, только на iOS

Мое приложение отображает некоторые изображения, которые я создал с помощью Image.createImage(). В некоторых случаях изображения полностью пустые, но только на iOS. Изображения работают нормально на Android. Кроме того, я создаю несколько изображений, используя Image.createImage (), и большинство из них работают нормально. Я не вижу разницы между тем и этим.

Для воспроизведения запустите прилагаемое приложение на Android и iOS. Приложение показывает два изображения. Второй взят из нижней половины первого. На Android изображения отображаются нормально. На iOS изображения появляются в течение нескольких секунд, а затем исчезают. Оказывается, они отображаются только во время отображения экрана запуска iOS. Как только он переключается на реальное приложение, изображения становятся пустыми, хотя они занимают одно и то же место. Дальнейшие тесты показывают, что изображения имеют правильный размер, но заполнены прозрачными пикселями.

Я должен сказать, что в моем реальном приложении изображения масштабируются по размеру экрана и окрашиваются в соответствии с предпочтениями пользователя, поэтому я не могу просто загрузить их из ресурса.

(Кстати, обратите внимание на изменение, которое я внес в метод stop. Это не связано, но стоит упомянуть.)

Вот контрольный пример:

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.io.Log;
import com.codename1.ui.Toolbar;
import java.util.Arrays;

/**
 * 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;
  private Resources theme;

  public void init(Object context) {
    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);
    container.add(new Label("Full Image:"));
    Image fullIcon = createFullImage(0x44ff00, 40, 30);
    Label fullImage = new Label(fullIcon);
    container.add(fullImage);

    container.add(new Label("---"));
    container.add(new Label("Half Image:"));
    Image halfIcon = createHalfSizeImage(fullIcon);
    Label halfImage = new Label(halfIcon);
    container.add(halfImage);

    return container;
  }

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

    // 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);


    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 createHalfSizeImage(Image fullImage) {
    int imageWidth = fullImage.getWidth();
    int imageHt = fullImage.getHeight();
    int[] rgbValues = fullImage.getRGB();
    // yeah, I've since discovered a much more sensible way to do this, but it doesn't fix the bug.
    int[] bottomHalf = Arrays.copyOfRange(rgbValues, rgbValues.length / 2, rgbValues.length);
    //noinspection StringConcatenation
    Log.p("Cutting side image from " + imageWidth + " x " + imageHt + " to " + imageWidth + " x " + (imageHt / 2));
    return Image.createImage(bottomHalf, imageWidth, imageHt / 2);
  }

  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;
  }
}

2 ответа

Решение

Вот обходной путь. Это работает, но не очень хорошо сглаживает. Это будет происходить, пока код iOS не будет исправлен.

Проблема, как описано в другом месте. Методы Graphics.fillArc() и drawArc() прекрасно работают на Android, но часто не работают на iOS. Вот поведение:

  • если width == height, они правильно рисуют круг.
  • если width < height, они должны нарисовать эллипс, но они рисуют круг, центрированный по предполагаемому эллипсу, с диаметром, равным width,
  • если width > heightони ничего не рисуют.

Обходной путь рисует круг на прозрачном фоне, затем рисует этот круг, сжатый в одном направлении к эллипсу, в нужное место. Он не очень хорошо справляется с сглаживанием, поэтому он не является хорошей заменой для рабочего кода, но будет работать до тех пор, пока ошибка не будет исправлена. (Этот обходной путь обрабатывает fillArc, но его нетрудно изменить для drawArc()

/**
 * Workaround for fillArc bug. Graphics.fillArc() works fine on android, but usually fails on iOS. There are three 
 * cases for its behavior.
 * 
 * If width < height, it draws a circle with a diameter equal to width, and concentric with the intended ellipse.<br>
 * If width > height, it draws nothing.<br>
 * If width == height, it works correctly.
 *
 * To work around this we create a separate image, draw a circle, re-proportion it to the proper ellipse, then draw 
 * it to the Graphics object. It doesn't anti-alias very well.
 */
public static void fillArcWorkaround(Graphics masterG, int x, int y, int width, int height, int startAngle, int arcAngle) {
    if (width == height) {
        masterG.fillArc(x, y, width, height, startAngle, arcAngle);
    } else {
        int max = Math.max(width, height);
        Image tempCircle = Image.createImage(max, max);
        Graphics tempG = tempCircle.getGraphics();
        tempG.setColor(masterG.getColor());
        tempG.fillRect(0, 0, max, max);

        // At this point tempCircle is just a colored rectangle. It becomes a circle when we apply the circle mask. The
        // region outside the circle becomes transparent that way.

        Image mask = Image.createImage(max, max);
        tempG = mask.getGraphics();
        tempG.setAntiAliased(masterG.isAntiAliased());
        tempG.setColor(0);
        tempG.fillRect(0, 0, max, max);
        tempG.setColor(0xFF); // blue
        tempG.fillArc(0, 0, max, max, startAngle, arcAngle);
        tempCircle = tempCircle.applyMask(mask.createMask());

        // Now tempCircle is a filled circle of the correct color. We now draw it in its intended proportions.

        masterG.setAntiAliased(true);
        masterG.drawImage(tempCircle, x, y, width, height);
    }
}

Это обсуждается в этом выпуске.

Обычно изображения изначально появляются из-за процесса скриншота, который показывает их, поэтому они никогда не отображаются на iOS изначально.

Распространенной причиной этих проблем является создание изображений из EDT, что, по-видимому, не является проблемой в этом конкретном коде.

Трудно понять, что происходит, поэтому я думаю, что нам нужно оценить проблему.

Другие вопросы по тегам