图形绘制代码生成空白图像,仅在 iOS
Graphics drawing code generates blank images, only on iOS
我的应用程序显示了一些我使用 Image.createImage() 创建的图像。在某些情况下,图像是完全空白的,但仅在 iOS 上。这些图像在 Android 上运行良好。此外,我使用 Image.createImage() 创建了多个图像,其中大部分都工作正常。我看不出它们之间有什么区别。
要重现,运行 Android 和 iOS 上随附的应用程序。该应用程序显示两个图像。第二个取自第一个的下半部分。在 Android 上,图像显示正常。在 iOS,图像出现了几秒钟,然后消失了。事实证明,它们只在 iOS 显示启动屏幕时出现。一旦它切换到实际的应用程序,图像是空白的,尽管它们占用相同的 space。进一步的测试表明图像尺寸正确,但充满了透明像素。
我应该说,在我的实际应用程序中,图像随屏幕大小缩放,并根据用户偏好着色,所以我不能只从资源中加载它们。
(顺便说一句,请注意我对停止方法所做的更改。这无关但值得一提。)
这是测试用例:
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;
}
}
这在 this issue 中讨论。
通常,图像最初出现是因为显示它们的屏幕截图过程,所以它们从来没有真正出现在 iOS 上。
这些问题的一个常见原因是从 EDT 创建图像,这似乎不是此特定代码中的问题。
很难看出发生了什么,所以我想我们需要评估这个问题。
这是一个解决方法。这有效,但不能很好地抗锯齿。直到 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);
}
}
我的应用程序显示了一些我使用 Image.createImage() 创建的图像。在某些情况下,图像是完全空白的,但仅在 iOS 上。这些图像在 Android 上运行良好。此外,我使用 Image.createImage() 创建了多个图像,其中大部分都工作正常。我看不出它们之间有什么区别。
要重现,运行 Android 和 iOS 上随附的应用程序。该应用程序显示两个图像。第二个取自第一个的下半部分。在 Android 上,图像显示正常。在 iOS,图像出现了几秒钟,然后消失了。事实证明,它们只在 iOS 显示启动屏幕时出现。一旦它切换到实际的应用程序,图像是空白的,尽管它们占用相同的 space。进一步的测试表明图像尺寸正确,但充满了透明像素。
我应该说,在我的实际应用程序中,图像随屏幕大小缩放,并根据用户偏好着色,所以我不能只从资源中加载它们。
(顺便说一句,请注意我对停止方法所做的更改。这无关但值得一提。)
这是测试用例:
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;
}
}
这在 this issue 中讨论。
通常,图像最初出现是因为显示它们的屏幕截图过程,所以它们从来没有真正出现在 iOS 上。
这些问题的一个常见原因是从 EDT 创建图像,这似乎不是此特定代码中的问题。
很难看出发生了什么,所以我想我们需要评估这个问题。
这是一个解决方法。这有效,但不能很好地抗锯齿。直到 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);
}
}