Skip to content

Commit 4f30b29

Browse files
Unify and improve image disablement calculation algorithm
Each OS-specific implementation of the Image class has its own implementation of an algorithm to calculate a disabled representation of a given image. In addition, the algorithm used on Windows and MacOS produces bad results, which is why usually pre-generated disabled icons are used. This change centralizes the implementations of those algorithms in ImageColorTransformers. The implementations used for the pre-generated disabled icons is added as default algorithm with an enhanced and the existing GTK-conforming algorithms being provided as options. Co-authored-by: Manuel Killinger <Manuel.Killinger@vector.com>
1 parent a6daf3f commit 4f30b29

File tree

4 files changed

+112
-39
lines changed

4 files changed

+112
-39
lines changed

bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/Image.java

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
package org.eclipse.swt.graphics;
1515

1616

17+
import static org.eclipse.swt.internal.image.ImageColorTransformer.DEFAULT_DISABLED_IMAGE_TRANSFORMER;
18+
1719
import java.io.*;
1820
import java.util.*;
1921
import java.util.function.*;
@@ -435,29 +437,21 @@ private void createRepFromSourceAndApplyFlag(NSBitmapImageRep srcRep, int srcWid
435437
long data = rep.bitmapData();
436438
C.memmove(data, srcData, srcWidth * srcHeight * 4);
437439
if (flag != SWT.IMAGE_COPY) {
438-
final int redOffset, greenOffset, blueOffset;
440+
final int redOffset, greenOffset, blueOffset, alphaOffset;
439441
if (srcBpp == 32 && (srcBitmapFormat & OS.NSAlphaFirstBitmapFormat) == 0) {
440442
redOffset = 0;
441443
greenOffset = 1;
442444
blueOffset = 2;
445+
alphaOffset = 3;
443446
} else {
447+
alphaOffset = 0;
444448
redOffset = 1;
445449
greenOffset = 2;
446450
blueOffset = 3;
447451
}
448452
/* Apply transformation */
449453
switch (flag) {
450454
case SWT.IMAGE_DISABLE: {
451-
Color zeroColor = this.device.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
452-
RGB zeroRGB = zeroColor.getRGB();
453-
byte zeroRed = (byte)zeroRGB.red;
454-
byte zeroGreen = (byte)zeroRGB.green;
455-
byte zeroBlue = (byte)zeroRGB.blue;
456-
Color oneColor = this.device.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
457-
RGB oneRGB = oneColor.getRGB();
458-
byte oneRed = (byte)oneRGB.red;
459-
byte oneGreen = (byte)oneRGB.green;
460-
byte oneBlue = (byte)oneRGB.blue;
461455
byte[] line = new byte[(int)srcBpr];
462456
for (int y=0; y<srcHeight; y++) {
463457
C.memmove(line, data + (y * srcBpr), srcBpr);
@@ -466,16 +460,12 @@ private void createRepFromSourceAndApplyFlag(NSBitmapImageRep srcRep, int srcWid
466460
int red = line[offset+redOffset] & 0xFF;
467461
int green = line[offset+greenOffset] & 0xFF;
468462
int blue = line[offset+blueOffset] & 0xFF;
469-
int intensity = red * red + green * green + blue * blue;
470-
if (intensity < 98304) {
471-
line[offset+redOffset] = zeroRed;
472-
line[offset+greenOffset] = zeroGreen;
473-
line[offset+blueOffset] = zeroBlue;
474-
} else {
475-
line[offset+redOffset] = oneRed;
476-
line[offset+greenOffset] = oneGreen;
477-
line[offset+blueOffset] = oneBlue;
478-
}
463+
int alpha = line[offset+alphaOffset] & 0xFF;
464+
RGBA result = DEFAULT_DISABLED_IMAGE_TRANSFORMER.adaptPixelValue(red, green, blue, alpha);
465+
line[offset+redOffset] = (byte) result.rgb.red;
466+
line[offset+greenOffset] = (byte) result.rgb.green;
467+
line[offset+blueOffset] = (byte) result.rgb.blue;
468+
line[offset+alphaOffset] = (byte) result.alpha;
479469
offset += 4;
480470
}
481471
C.memmove(data + (y * srcBpr), line, srcBpr);
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Vector Informatik GmbH and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
package org.eclipse.swt.internal.image;
12+
13+
import org.eclipse.swt.*;
14+
import org.eclipse.swt.graphics.*;
15+
16+
public interface ImageColorTransformer {
17+
18+
public static final String IMAGE_DISABLEMENT_ALGORITHM_GRAYED = "grayed";
19+
public static final String IMAGE_DISABLEMENT_ALGORITHM_GTK = "gtk";
20+
public static final String IMAGE_DISABLEMENT_ALGORITHM_DESATURATED = "desaturated";
21+
public static final String IMAGE_DISABLEMENT_ALGORITHM = System.getProperty("org.eclipse.swt.image.disablement",
22+
IMAGE_DISABLEMENT_ALGORITHM_GRAYED).strip();
23+
24+
public static final ImageColorTransformer DEFAULT_DISABLED_IMAGE_TRANSFORMER = switch (IMAGE_DISABLEMENT_ALGORITHM) {
25+
case IMAGE_DISABLEMENT_ALGORITHM_GTK -> ImageColorTransformer.forRGB(0.5f, 0.5f, 0.5f, 0.5f);
26+
case IMAGE_DISABLEMENT_ALGORITHM_DESATURATED -> ImageColorTransformer.forHSB(1.0f, 0.2f, 0.9f, 0.5f);
27+
default -> ImageColorTransformer.forGrayscaledContrastBrightness(0.2f, 2.9f);
28+
};
29+
30+
RGBA adaptPixelValue(int red, int green, int blue, int alpha);
31+
32+
public static ImageColorTransformer forHSB(float hueFactor, float saturationFactor, float brightnessFactor,
33+
float alphaFactor) {
34+
return (red, green, blue, alpha) -> {
35+
float[] hsba = new RGBA(red, green, blue, alpha).getHSBA();
36+
float hue = Math.min(hueFactor * hsba[0], 1.0f);
37+
float saturation = Math.min(saturationFactor * hsba[1], 1.0f);
38+
float brightness = Math.min(brightnessFactor * hsba[2], 1.0f);
39+
float alphaResult = Math.min(alphaFactor * hsba[3], 255.0f);
40+
return new RGBA(hue, saturation, brightness, alphaResult);
41+
};
42+
}
43+
44+
public static ImageColorTransformer forRGB(float redFactor, float greenFactor, float blueFactor,
45+
float alphaFactor) {
46+
return (red, green, blue, alpha) -> {
47+
int redResult = (int) Math.min(redFactor * red, 255.0f);
48+
int greenResult = (int) Math.min(greenFactor * green, 255.0f);
49+
int blueResult = (int) Math.min(blueFactor * blue, 255.0f);
50+
int alphaResult = (int) Math.min(alphaFactor * alpha, 255.0f);
51+
return new RGBA(redResult, greenResult, blueResult, alphaResult);
52+
};
53+
}
54+
55+
public static ImageColorTransformer forIntensityThreshold(Device device) {
56+
RGBA lowIntensity = device.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW).getRGBA();
57+
RGBA highIntensity = device.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND).getRGBA();
58+
return (red, green, blue, alpha) -> {
59+
int intensity = red * red + green * green + blue * blue;
60+
RGBA usedGraytone = intensity < 98304 ? lowIntensity : highIntensity;
61+
return new RGBA(usedGraytone.rgb.red, usedGraytone.rgb.green, usedGraytone.rgb.blue, alpha);
62+
};
63+
}
64+
65+
public static ImageColorTransformer forGrayscaledContrastBrightness(float contrast, float brightness) {
66+
return (red, green, blue, alpha) -> {
67+
int grayValue = Math.min((77 * red + 151 * green + 28 * blue) / 255, 255);
68+
int resultValue = (int) Math.min(Math.max((contrast * (grayValue * brightness - 128) + 128), 0), 255);
69+
return new RGBA(resultValue, resultValue, resultValue, alpha);
70+
};
71+
}
72+
73+
}

bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/Image.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
package org.eclipse.swt.graphics;
1515

1616

17+
import static org.eclipse.swt.internal.image.ImageColorTransformer.DEFAULT_DISABLED_IMAGE_TRANSFORMER;
18+
1719
import java.io.*;
1820
import java.util.*;
1921

@@ -307,15 +309,30 @@ public Image(Device device, Image srcImage, int flag) {
307309
byte[] line = new byte[stride];
308310
for (int y=0; y<height; y++) {
309311
C.memmove(line, data + (y * stride), stride);
310-
for (int x=0, offset=0; x<width; x++, offset += 4) {
312+
for (int x = 0, offset = 0; x < width; x++, offset += 4) {
311313
int a = line[offset + oa] & 0xFF;
312314
int r = line[offset + or] & 0xFF;
313315
int g = line[offset + og] & 0xFF;
314316
int b = line[offset + ob] & 0xFF;
315-
line[offset + oa] = (byte) Math.round((double) a * 0.5);
316-
line[offset + or] = (byte) Math.round((double) r * 0.5);
317-
line[offset + og] = (byte) Math.round((double) g * 0.5);
318-
line[offset + ob] = (byte) Math.round((double) b * 0.5);
317+
// The alpha value is embedded into the RGB values as well, so extract it out
318+
// of those values for transformation and reapply it afterwards
319+
// Note: don't change execution order, e.g., using *= assignment, as this is
320+
// integer arithmetics
321+
if (hasAlpha && a != 0) {
322+
r = r * 255 / a;
323+
g = g * 255 / a;
324+
b = b * 255 / a;
325+
}
326+
RGBA result = DEFAULT_DISABLED_IMAGE_TRANSFORMER.adaptPixelValue(r, g, b, a);
327+
if (hasAlpha) {
328+
result.rgb.red = result.rgb.red * result.alpha / 255;
329+
result.rgb.green = result.rgb.green * result.alpha / 255;
330+
result.rgb.blue = result.rgb.blue * result.alpha / 255;
331+
}
332+
line[offset + oa] = (byte) result.alpha;
333+
line[offset + or] = (byte) result.rgb.red;
334+
line[offset + og] = (byte) result.rgb.green;
335+
line[offset + ob] = (byte) result.rgb.blue;
319336
}
320337
C.memmove(data + (y * stride), line, stride);
321338
}

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
package org.eclipse.swt.graphics;
1515

1616

17+
import static org.eclipse.swt.internal.image.ImageColorTransformer.DEFAULT_DISABLED_IMAGE_TRANSFORMER;
18+
1719
import java.io.*;
1820
import java.util.*;
1921
import java.util.function.*;
@@ -669,11 +671,7 @@ void init() {
669671

670672
private ImageData applyDisableImageData(ImageData data, int height, int width) {
671673
PaletteData palette = data.palette;
672-
RGB[] rgbs = new RGB[3];
673-
rgbs[0] = this.device.getSystemColor(SWT.COLOR_BLACK).getRGB();
674-
rgbs[1] = this.device.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW).getRGB();
675-
rgbs[2] = this.device.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND).getRGB();
676-
ImageData newData = new ImageData(width, height, 8, new PaletteData(rgbs));
674+
ImageData newData = new ImageData(width, height, 32, new PaletteData(0xFF, 0xFF00, 0xFF0000));
677675
newData.alpha = data.alpha;
678676
newData.alphaData = data.alphaData;
679677
newData.maskData = data.maskData;
@@ -693,7 +691,6 @@ private ImageData applyDisableImageData(ImageData data, int height, int width) {
693691
int greenShift = palette.greenShift;
694692
int blueShift = palette.blueShift;
695693
for (int y=0; y<height; y++) {
696-
int offset = y * newData.bytesPerLine;
697694
data.getPixels(0, y, width, scanline, 0);
698695
if (mask != null) mask.getPixels(0, y, width, maskScanline, 0);
699696
for (int x=0; x<width; x++) {
@@ -712,14 +709,10 @@ private ImageData applyDisableImageData(ImageData data, int height, int width) {
712709
green = palette.colors[pixel].green;
713710
blue = palette.colors[pixel].blue;
714711
}
715-
int intensity = red * red + green * green + blue * blue;
716-
if (intensity < 98304) {
717-
newData.data[offset] = (byte)1;
718-
} else {
719-
newData.data[offset] = (byte)2;
720-
}
712+
RGBA result = DEFAULT_DISABLED_IMAGE_TRANSFORMER.adaptPixelValue(red, green, blue, data.getAlpha(x, y));
713+
newData.setAlpha(x, y, result.alpha);
714+
newData.setPixel(x, y, newData.palette.getPixel(result.rgb));
721715
}
722-
offset++;
723716
}
724717
}
725718
return newData;

0 commit comments

Comments
 (0)