Skip to content

Commit

Permalink
Show a warning if the image has an empty alpha channel.
Browse files Browse the repository at this point in the history
If the image has an alpha channel, where all values are below a threshold,
a warning and option to disable the alpha channel are shown. If the alpha
channel is disabled through this option and later an image with an alpha
channel is selected, a warning and option to re-enable the alpha channel
are shown.

Fixes #723
  • Loading branch information
pmuetschard committed Oct 12, 2017
1 parent 0dad9d0 commit 944094d
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 9 deletions.
76 changes: 72 additions & 4 deletions gapic/src/main/com/google/gapid/image/ArrayImage.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.google.gapid.util.Colors.DARK_LUMINANCE_THRESHOLD;
import static com.google.gapid.util.Colors.clamp;

import com.google.common.primitives.UnsignedBytes;
import com.google.gapid.glviewer.gl.Texture;
import com.google.gapid.util.Colors;

Expand Down Expand Up @@ -168,8 +169,11 @@ public Builder flip() {
* An {@link ArrayImage} that represents an RGBA image with 8bit color channels.
*/
public static class RGBA8Image extends ArrayImage {
private final PixelInfo info;

public RGBA8Image(int width, int height, int depth, byte[] data) {
super(width, height, depth, 4, data, GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
this.info = IntPixelInfo.compute(data);
}

@Override
Expand Down Expand Up @@ -202,7 +206,7 @@ protected PixelValue getPixel(int x, int y, byte[] data) {

@Override
public PixelInfo getInfo() {
return PixelInfo.NULL_INFO;
return info;
}

private static class Pixel implements PixelValue {
Expand Down Expand Up @@ -420,10 +424,13 @@ public boolean isDark() {

private static class FloatPixelInfo implements PixelInfo {
private final float min, max;
private final float alphaMin, alphaMax;

private FloatPixelInfo(float min, float max) {
private FloatPixelInfo(float min, float max, float alphaMin, float alphaMax) {
this.min = min;
this.max = max;
this.alphaMin = alphaMin;
this.alphaMax = alphaMax;
}

public static PixelInfo compute(FloatBuffer buffer, boolean isRGBA) {
Expand All @@ -432,7 +439,10 @@ public static PixelInfo compute(FloatBuffer buffer, boolean isRGBA) {
}

float min = Float.POSITIVE_INFINITY, max = Float.NEGATIVE_INFINITY;
float alphaMin, alphaMax;
if (isRGBA) {
alphaMin = Float.POSITIVE_INFINITY;
alphaMax = Float.NEGATIVE_INFINITY;
for (int i = 0, end = buffer.remaining() - 3; i <= end; ) {
float value = buffer.get(i++);
if (!Float.isNaN(value) && !Float.isInfinite(value)) {
Expand All @@ -449,9 +459,14 @@ public static PixelInfo compute(FloatBuffer buffer, boolean isRGBA) {
min = Math.min(min, value);
max = Math.max(max, value);
}
i++; // skip alpha
value = buffer.get(i++);
if (!Float.isNaN(value) && !Float.isInfinite(value)) {
alphaMin = Math.min(alphaMin, value);
alphaMax = Math.max(alphaMax, value);
}
}
} else {
alphaMin = alphaMax = 1;
for (int i = 0; i < buffer.remaining(); i++) {
float value = buffer.get(i);
if (!Float.isNaN(value) && !Float.isInfinite(value)) {
Expand All @@ -460,7 +475,7 @@ public static PixelInfo compute(FloatBuffer buffer, boolean isRGBA) {
}
}
}
return new FloatPixelInfo(min, max);
return new FloatPixelInfo(min, max, alphaMin, alphaMax);
}

@Override
Expand All @@ -472,5 +487,58 @@ public float getMin() {
public float getMax() {
return max;
}

@Override
public float getAlphaMin() {
return alphaMin;
}

@Override
public float getAlphaMax() {
return alphaMax;
}
}

private static class IntPixelInfo implements PixelInfo {
private final float alphaMin, alphaMax;

private IntPixelInfo(float alphaMin, float alphaMax) {
this.alphaMin = alphaMin;
this.alphaMax = alphaMax;
}

public static PixelInfo compute(byte[] rgba) {
if (rgba.length == 0) {
return PixelInfo.NULL_INFO;
}

int alphaMin = Integer.MAX_VALUE, alphaMax = Integer.MIN_VALUE;
for (int i = 3; i < rgba.length; i += 4) {
int value = UnsignedBytes.toInt(rgba[i]);
alphaMin = Math.min(alphaMin, value);
alphaMax = Math.max(alphaMax, value);
}
return new IntPixelInfo(alphaMin / 255f, alphaMax / 255f);
}

@Override
public float getMin() {
return 0; // Disable automatic tone-mapping.
}

@Override
public float getMax() {
return 1; // Disable automatic tone-mapping.
}

@Override
public float getAlphaMin() {
return alphaMin;
}

@Override
public float getAlphaMax() {
return alphaMax;
}
}
}
20 changes: 20 additions & 0 deletions gapic/src/main/com/google/gapid/image/Image.java
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,16 @@ public float getMin() {
public float getMax() {
return 1;
}

@Override
public float getAlphaMin() {
return 1;
}

@Override
public float getAlphaMax() {
return 1;
}
};

/**
Expand All @@ -164,5 +174,15 @@ public float getMax() {
* @return the maximum value across all channels of the image data. Used for tone mapping.
*/
public float getMax();

/**
* @return the minimum alpha value of the image data.
*/
public float getAlphaMin();

/**
* @return the maximum alpha value of the image data.
*/
public float getAlphaMax();
}
}
96 changes: 91 additions & 5 deletions gapic/src/main/com/google/gapid/widgets/ImagePanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import static com.google.gapid.widgets.Widgets.createSeparator;
import static com.google.gapid.widgets.Widgets.createToggleToolItem;
import static com.google.gapid.widgets.Widgets.createToolItem;
import static com.google.gapid.widgets.Widgets.withSpans;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
Expand Down Expand Up @@ -84,6 +85,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.logging.Logger;

Expand All @@ -94,6 +96,7 @@ public class ImagePanel extends Composite {
protected static final Logger LOG = Logger.getLogger(ImagePanel.class.getName());
protected static final int ZOOM_AMOUNT = 5;
private static final int CHANNEL_RED = 0, CHANNEL_GREEN = 1, CHANNEL_BLUE = 2, CHANNEL_ALPHA = 3;
private static final float ALPHA_WARNING_THRESHOLD = 2 / 255f;
private static final Image[] NO_LAYERS = new Image[] { Image.EMPTY };

private final SingleInFlight imageRequestController = new SingleInFlight();
Expand All @@ -112,8 +115,8 @@ public ImagePanel(Composite parent, Widgets widgets, boolean naturallyFlipped) {
setLayout(Widgets.withMargin(new GridLayout(1, false), 5, 2));

loading = LoadablePanel.create(this, widgets, panel ->
new ImageComponent(panel, widgets.theme, naturallyFlipped));
status = new StatusBar(this, this::loadLevel);
new ImageComponent(panel, widgets.theme, this::showAlphaWarning, naturallyFlipped));
status = new StatusBar(this, widgets.theme, this::loadLevel, this::setAlphaEnabled);
imageComponent = loading.getContents();

loading.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Expand Down Expand Up @@ -185,6 +188,14 @@ protected void setPreviewPixel(Pixel pixel) {
status.setPixel(pixel);
}

private void showAlphaWarning(AlphaWarning message) {
status.showAlphaWarning(message);
}

private void setAlphaEnabled(boolean enabled) {
imageComponent.autoToggleAlphaChannel(enabled);
}

public void createToolbar(ToolBar bar, Theme theme) {
zoomFitItem = createToggleToolItem(bar, theme.zoomFit(),
e -> setZoomToFit(((ToolItem)e.widget).getSelection()), "Zoom to fit");
Expand Down Expand Up @@ -400,6 +411,7 @@ private static class ImageComponent extends Composite {
private static final double MAX_ZOOM_FACTOR = 8;
private static final VecD MIN_ZOOM_SIZE = new VecD(100, 100, 0);

private final Consumer<AlphaWarning> showAlphaWarning;
private final boolean naturallyFlipped;

private final ScrollBar scrollbars[];
Expand All @@ -422,10 +434,14 @@ private static class ImageComponent extends Composite {
private double scaleGridToView = 1.0;
private boolean zoomToFit;

public ImageComponent(Composite parent, Theme theme, boolean naturallyFlipped) {
private boolean alphaWasAutoDisabled = false;

public ImageComponent(Composite parent, Theme theme, Consumer<AlphaWarning> showAlphaWarning,
boolean naturallyFlipped) {
super(parent, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.NO_BACKGROUND);
setLayout(new FillLayout(SWT.VERTICAL));

this.showAlphaWarning = showAlphaWarning;
this.naturallyFlipped = naturallyFlipped;

scrollbars = new ScrollBar[] { getHorizontalBar(), getVerticalBar() };
Expand Down Expand Up @@ -492,6 +508,33 @@ private void refresh() {
data.images = images;
data.transforms = calcTransforms();
canvas.setSceneData(data.copy());

if (images.length == 0) {
showAlphaWarning.accept(AlphaWarning.NONE);
} else if (isChannelEnabled(CHANNEL_ALPHA)) {
boolean noAlpha = true;
for (Image image : images) {
if (image.getInfo().getAlphaMax() > ALPHA_WARNING_THRESHOLD) {
noAlpha = false;
break;
}
}
showAlphaWarning.accept(noAlpha ? AlphaWarning.NO_ALPHA : AlphaWarning.NONE);
} else if (alphaWasAutoDisabled) {
boolean noAlpha = true;
for (Image image : images) {
PixelInfo info = image.getInfo();
if (info.getAlphaMax() > ALPHA_WARNING_THRESHOLD &&
info.getAlphaMin() < 1 - ALPHA_WARNING_THRESHOLD) {
// Consider an image with all alpha values mostly 1.0 as an image without alpha.
noAlpha = false;
break;
}
}
showAlphaWarning.accept(noAlpha ? AlphaWarning.NONE : AlphaWarning.ALPHA_DISABLED);
} else {
showAlphaWarning.accept(AlphaWarning.NONE);
}
}

public void setPreviewPixel(Pixel previewPixel) {
Expand Down Expand Up @@ -524,6 +567,15 @@ protected boolean isChannelEnabled(int channel) {

protected void setChannelEnabled(int channel, boolean enabled) {
data.channels[channel] = enabled;
if (channel == CHANNEL_ALPHA) {
alphaWasAutoDisabled = false;
}
refresh();
}

protected void autoToggleAlphaChannel(boolean enabled) {
alphaWasAutoDisabled = true;
data.channels[CHANNEL_ALPHA] = enabled;
refresh();
}

Expand Down Expand Up @@ -709,7 +761,6 @@ private static class ImageScene implements Scene<SceneData> {
private static final int PREVIEW_HEIGHT = 11; // Should be odd, so center pixel looks nice.
private static final int PREVIEW_SIZE = 7;


private final Map<Image, Texture> imageToTexture = Maps.newHashMap();
private Shader shader;
private Texture[] textures;
Expand Down Expand Up @@ -964,9 +1015,12 @@ private static class StatusBar extends Composite {
private final Label levelValue;
private final Label levelSize;
private final Label pixelLabel;
private final Label warning;
private int lastSelection = 0;
private AlphaWarning lastWarning = AlphaWarning.NONE;

public StatusBar(Composite parent, IntConsumer levelListener) {
public StatusBar(
Composite parent, Theme theme, IntConsumer levelListener, Consumer<Boolean> enableAlpha) {
super(parent, SWT.NONE);
setLayout(new GridLayout(3, false));

Expand All @@ -976,10 +1030,15 @@ public StatusBar(Composite parent, IntConsumer levelListener) {
levelScale = createScale(levelComposite);
levelSize = createLabel(this, "");
pixelLabel = createLabel(this, "");
warning = createLabel(this, "");
warning.setForeground(theme.imageWarning());
warning.setCursor(getDisplay().getSystemCursor(SWT.CURSOR_HAND));

levelComposite.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, true));
levelSize.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, true));
pixelLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
warning.setLayoutData(withSpans(new GridData(SWT.CENTER, SWT.CENTER, true, false), 3, 1));
showAlphaWarning(AlphaWarning.NONE);

levelScale.addListener(SWT.Selection, e -> {
int selection = levelScale.getSelection();
Expand All @@ -990,6 +1049,9 @@ public StatusBar(Composite parent, IntConsumer levelListener) {
levelListener.accept(selection);
}
});
warning.addListener(SWT.MouseUp,
e -> enableAlpha.accept(lastWarning == AlphaWarning.ALPHA_DISABLED));

setLevelCount(0);
}

Expand Down Expand Up @@ -1019,6 +1081,18 @@ public void setPixel(Pixel pixel) {
requestLayout();
}

public void showAlphaWarning(AlphaWarning message) {
if (lastWarning == message) {
return;
}

lastWarning = message;
((GridData)warning.getLayoutData()).exclude = message == AlphaWarning.NONE;
warning.setVisible(message != AlphaWarning.NONE);
warning.setText(message.warning);
requestLayout();
}

private static Scale createScale(Composite parent) {
Scale scale = new Scale(parent, SWT.HORIZONTAL);
scale.setMinimum(0);
Expand All @@ -1029,4 +1103,16 @@ private static Scale createScale(Composite parent) {
return scale;
}
}

private static enum AlphaWarning {
NONE(""),
NO_ALPHA("The alpha channels appears to be empty. Click to disable the alpha channel."),
ALPHA_DISABLED("Image contains an alpha channel. Click to re-enable alpha channel.");

public final String warning;

private AlphaWarning(String warning) {
this.warning = warning;
}
}
}
1 change: 1 addition & 0 deletions gapic/src/main/com/google/gapid/widgets/Theme.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ public interface Theme {
@RGB(argb = 0xffffffff) public Color imageCheckerLight();
@RGB(argb = 0xff000000) public Color imageCursorDark();
@RGB(argb = 0xffffffff) public Color imageCursorLight();
@RGB(argb = 0xffff9900) public Color imageWarning();

@TextStyle(foreground = 0xa9a9a9) public Styler structureStyler();
@TextStyle(foreground = 0x0000ee) public Styler identifierStyler();
Expand Down

0 comments on commit 944094d

Please sign in to comment.