11import java .awt .*;
22import java .awt .image .BufferedImage ;
3+ import java .awt .image .ImageObserver ;
4+ import java .awt .image .PixelGrabber ;
35import java .io .*;
46import java .nio .file .*;
57import java .time .LocalDateTime ;
@@ -31,6 +33,8 @@ public class AvdSkinToCodenameOneSkin {
3133 private static final double TABLET_INCH_THRESHOLD = 6.5d ;
3234
3335 public static void main (String [] args ) throws Exception {
36+ System .setProperty ("java.awt.headless" , "true" );
37+
3438 if (args .length == 0 || args .length > 2 ) {
3539 System .err .println ("Usage: java AvdSkinToCodenameOneSkin.java <avd-skin-dir> [output.skin]" );
3640 System .exit (1 );
@@ -132,10 +136,7 @@ private static DeviceImages buildDeviceImages(Path skinDir, OrientationInfo orie
132136 throw new IllegalStateException ("Missing image '" + orientation .imageName () + "' for " + orientation .orientation ());
133137 }
134138 try {
135- BufferedImage original = javax .imageio .ImageIO .read (imagePath .toFile ());
136- if (original == null ) {
137- throw new IllegalStateException ("Failed to decode image " + imagePath );
138- }
139+ BufferedImage original = readImage (imagePath );
139140 if (orientation .display ().width () <= 0 || orientation .display ().height () <= 0 ) {
140141 throw new IllegalStateException ("Invalid display dimensions for " + orientation .orientation ());
141142 }
@@ -145,6 +146,53 @@ private static DeviceImages buildDeviceImages(Path skinDir, OrientationInfo orie
145146 }
146147 }
147148
149+ private static BufferedImage readImage (Path imagePath ) throws IOException {
150+ BufferedImage standard = javax .imageio .ImageIO .read (imagePath .toFile ());
151+ if (standard != null ) {
152+ return standard ;
153+ }
154+
155+ byte [] data = Files .readAllBytes (imagePath );
156+ Image toolkitImage ;
157+ try {
158+ toolkitImage = Toolkit .getDefaultToolkit ().createImage (data );
159+ } catch (HeadlessException err ) {
160+ throw new IllegalStateException ("Unsupported image format for " + imagePath + " (headless toolkit)" , err );
161+ }
162+ if (toolkitImage == null ) {
163+ throw new IllegalStateException ("Unsupported image format for " + imagePath );
164+ }
165+ PixelGrabber grabber = new PixelGrabber (toolkitImage , 0 , 0 , -1 , -1 , true );
166+ try {
167+ grabber .grabPixels ();
168+ } catch (InterruptedException err ) {
169+ Thread .currentThread ().interrupt ();
170+ throw new IOException ("Interrupted while decoding " + imagePath , err );
171+ }
172+ if (grabber .getStatus () != ImageObserver .ALLBITS ) {
173+ throw new IllegalStateException ("Failed to decode image " + imagePath );
174+ }
175+ int width = grabber .getWidth ();
176+ int height = grabber .getHeight ();
177+ if (width <= 0 || height <= 0 ) {
178+ throw new IllegalStateException ("Failed to decode image " + imagePath );
179+ }
180+ BufferedImage result = new BufferedImage (width , height , BufferedImage .TYPE_INT_ARGB );
181+ Object pixels = grabber .getPixels ();
182+ if (!(pixels instanceof int [] rgb )) {
183+ throw new IllegalStateException ("Unsupported pixel model in " + imagePath );
184+ }
185+ Graphics2D g = result .createGraphics ();
186+ try {
187+ g .setComposite (AlphaComposite .Src );
188+ g .drawImage (toolkitImage , 0 , 0 , null );
189+ result .setRGB (0 , 0 , width , height , rgb , 0 , width );
190+ } finally {
191+ g .dispose ();
192+ }
193+ return result ;
194+ }
195+
148196 private static void writeEntry (ZipOutputStream zos , String name , BufferedImage image ) throws IOException {
149197 ZipEntry entry = new ZipEntry (name );
150198 zos .putNextEntry (entry );
@@ -273,7 +321,7 @@ private void handleKeyValue(String line) {
273321 String key = parts [0 ];
274322 String value = unquote (parts [1 ]);
275323 String ctxName = ctx .name .toLowerCase (Locale .ROOT );
276- if (ctxName .contains ("image" ) && key . equalsIgnoreCase ( "name" )) {
324+ if (ctxName .contains ("image" ) && isImageKey ( key )) {
277325 builder .considerImage (value , contextStack , this ::resolveImagePath );
278326 } else if (ctxName .contains ("display" )) {
279327 switch (key .toLowerCase (Locale .ROOT )) {
@@ -285,6 +333,11 @@ private void handleKeyValue(String line) {
285333 }
286334 }
287335
336+ private boolean isImageKey (String key ) {
337+ String lower = key .toLowerCase (Locale .ROOT );
338+ return lower .equals ("name" ) || lower .equals ("image" ) || lower .equals ("filename" );
339+ }
340+
288341 private void pushContext (String name ) {
289342 name = name .trim ();
290343 if (name .isEmpty ()) {
@@ -409,18 +462,18 @@ static ImageCandidate from(String name, Deque<Context> contexts, java.util.funct
409462 boolean controlHint = false ;
410463 for (Context ctx : contexts ) {
411464 String lower = ctx .name .toLowerCase (Locale .ROOT );
412- if (lower .contains ("button" ) || lower .contains ("control" ) || lower .contains ("icon" ) || lower .contains ("touch" )) {
465+ if (lower .contains ("button" ) || lower .contains ("control" ) || lower .contains ("icon" ) || lower .contains ("touch" ) || lower . contains ( "shadow" ) || lower . contains ( "onion" ) ) {
413466 controlHint = true ;
414467 }
415- if (lower .contains ("device" ) || lower .contains ("frame" ) || lower .contains ("skin" ) || lower .contains ("phone" ) || lower .contains ("tablet" )) {
468+ if (lower .contains ("device" ) || lower .contains ("frame" ) || lower .contains ("skin" ) || lower .contains ("phone" ) || lower .contains ("tablet" ) || lower . contains ( "background" ) || lower . contains ( "back" ) ) {
416469 frameHint = true ;
417470 }
418471 }
419472 String lowerName = name .toLowerCase (Locale .ROOT );
420- if (lowerName .contains ("frame" ) || lowerName .contains ("device" ) || lowerName .contains ("shell" ) || lowerName .contains ("body" )) {
473+ if (lowerName .contains ("frame" ) || lowerName .contains ("device" ) || lowerName .contains ("shell" ) || lowerName .contains ("body" ) || lowerName . contains ( "background" ) || lowerName . contains ( "back" ) || lowerName . contains ( "fore" ) ) {
421474 frameHint = true ;
422475 }
423- if (lowerName .contains ("button" ) || lowerName .contains ("control" ) || lowerName .contains ("icon" )) {
476+ if (lowerName .contains ("button" ) || lowerName .contains ("control" ) || lowerName .contains ("icon" ) || lowerName . contains ( "shadow" ) || lowerName . contains ( "onion" ) ) {
424477 controlHint = true ;
425478 }
426479 long area = computeArea (resolver .apply (name ));
0 commit comments