Java – konwersja obrazków SWT na AWT (i vice versa) z przezroczystością

2

Konwersja obrazków biblioteki SWT na obrazki AWT może przysporzyć pewnych problemów. Ani jedna ani druga biblioteka nie udostępnia gotowych rozwiązań do tego, jedynie na stronie SWT Snippets możemy znaleźć kod, który takową operację wykonuje. Niestety, kod ten nie jest jednak idealny. Przede wszystkim, konwersja SWT => AWT jest wykonywana bez uwzględnienia przezroczystości (co ciekawe, w drugą stronę już działa). W tym krótkim artykule postaram się opisać niezbędne czynności, aby tę – jakże ważną – funkcjonalność przywrócić.

Wspomniany kod znaleźć można tutaj.

Kod odpowiedzialny za konwersję obrazka SWT na AWT wygląda standardowo tak:

static BufferedImage convertToAWT(ImageData data) {
	ColorModel colorModel = null;
	PaletteData palette = data.palette;
	if (palette.isDirect) {
		colorModel = new DirectColorModel(data.depth, palette.redMask, palette.greenMask, palette.blueMask);
		BufferedImage bufferedImage = new BufferedImage(colorModel, colorModel.createCompatibleWritableRaster(data.width, data.height), false, null);
		for (int y = 0; y < data.height; y++) {
			for (int x = 0; x < data.width; x++) {
				int pixel = data.getPixel(x, y);
				RGB rgb = palette.getRGB(pixel);
				bufferedImage.setRGB(x, y,  rgb.red << 16 | rgb.green << 8 | rgb.blue);
			}
		}
		return bufferedImage;
	} else {
		RGB[] rgbs = palette.getRGBs();
		byte[] red = new byte[rgbs.length];
		byte[] green = new byte[rgbs.length];
		byte[] blue = new byte[rgbs.length];
		for (int i = 0; i < rgbs.length; i++) {
			RGB rgb = rgbs[i];
			red[i] = (byte)rgb.red;
			green[i] = (byte)rgb.green;
			blue[i] = (byte)rgb.blue;
		}
		if (data.transparentPixel != -1) {
			colorModel = new IndexColorModel(data.depth, rgbs.length, red, green, blue, data.transparentPixel);
		} else {
			colorModel = new IndexColorModel(data.depth, rgbs.length, red, green, blue);
		}		
		BufferedImage bufferedImage = new BufferedImage(colorModel, colorModel.createCompatibleWritableRaster(data.width, data.height), false, null);
		WritableRaster raster = bufferedImage.getRaster();
		int[] pixelArray = new int[1];
		for (int y = 0; y < data.height; y++) {
			for (int x = 0; x < data.width; x++) {
				int pixel = data.getPixel(x, y);
				pixelArray[0] = pixel;
				raster.setPixel(x, y, pixelArray);
			}
		}
		return bufferedImage;
	}
}

Fragment, który nas najbardziej interesuje to przede wszystkim ten:

	if (palette.isDirect) {
		colorModel = new DirectColorModel(data.depth, palette.redMask, palette.greenMask, palette.blueMask);
		BufferedImage bufferedImage = new BufferedImage(colorModel, colorModel.createCompatibleWritableRaster(data.width, data.height), false, null);
		for (int y = 0; y < data.height; y++) {
			for (int x = 0; x < data.width; x++) {
				int pixel = data.getPixel(x, y);
				RGB rgb = palette.getRGB(pixel);
				bufferedImage.setRGB(x, y,  rgb.red << 16 | rgb.green << 8 | rgb.blue);
			}
		}
		return bufferedImage;
	}

To właśnie ten kod odpowiada za konwersję większości obrazków. W naszym przypadku nawet wszystkich, bowiem jest to kod odpowiedzialny za konwersję obrazków z paletą bezpośrednią (oddzielne RGB dla każdego piksela); kod odpowiedzialny za konwersję obrazków z paletą indeksowaną (jak np. GIF) działa dobrze… 😉

Co jest tutaj nie tak?

  • Pobierany jest ColorModel – problem w tym, że on nie zadziała poprawnie w przypadku obrazów z alphą – utworzy BufferedImage z typem obrazu RGB zamiast ARGB
  • Do wynikowego BufferedImage’a nie jest zapisywana alpha

Poprawmy więc to!

2 linijki odpowiedzialne za utworzenie BufferedImage’a można zastąpić jedną:

BufferedImage bufferedImage = new BufferedImage(data.width, data.height, BufferedImage.TYPE_INT_ARGB);

Nie jest to co prawda rozwiązanie idealne, jednakże zadowalające, ponieważ funkcja zadziała dla absolutnie wszystkich obrazów – czy to z alphą, czy nie.

Przejdźmy teraz do ustawiania wartości RGB pikselom. Jak już wspominałem, algorytm nie uwzględnia alphy. Nie znajdziemy jej również w klasie RGB. Posłużymy się zatem metodą:

int ImageData.getAlpha(int x, int y)

której wynik następnie wstawimy w pierwszym bajcie 3. argumentu przekazywanego metodzie BufferedImage.setRGB(). Innymi słowy – linijka:

bufferedImage.setRGB(x, y,  rgb.red << 16 | rgb.green << 8 | rgb.blue);

Powinna wyglądać tak:

bufferedImage.setRGB(x, y, data.getAlpha(x, y) << 24 | rgb.red << 16 | rgb.green << 8 | rgb.blue);

Ot – i cały problem rozwiązany…

Cała funkcja powinna wyglądać więc tak jak poniżej:

static BufferedImage convertToAWT(ImageData data) {
		ColorModel colorModel = null;
		PaletteData palette = data.palette;
		if (palette.isDirect) {
			BufferedImage bufferedImage = new BufferedImage(data.width,
					data.height, BufferedImage.TYPE_INT_ARGB);
			for (int y = 0; y < data.height; y++) {
				for (int x = 0; x < data.width; x++) {
					int pixel = data.getPixel(x, y);
					RGB rgb = palette.getRGB(pixel);
					bufferedImage.setRGB(x, y, data.getAlpha(x, y) << 24
							| rgb.red << 16 | rgb.green << 8 | rgb.blue);
				}
			}
			return bufferedImage;
		} else {
			RGB[] rgbs = palette.getRGBs();
			byte[] red = new byte[rgbs.length];
			byte[] green = new byte[rgbs.length];
			byte[] blue = new byte[rgbs.length];
			for (int i = 0; i < rgbs.length; i++) {
				RGB rgb = rgbs[i];
				red[i] = (byte) rgb.red;
				green[i] = (byte) rgb.green;
				blue[i] = (byte) rgb.blue;
			}
			if (data.transparentPixel != -1) {
				colorModel = new IndexColorModel(data.depth, rgbs.length, red,
						green, blue, data.transparentPixel);
			} else {
				colorModel = new IndexColorModel(data.depth, rgbs.length, red,
						green, blue);
			}
			BufferedImage bufferedImage = new BufferedImage(colorModel,
					colorModel.createCompatibleWritableRaster(data.width,
							data.height), false, null);
			WritableRaster raster = bufferedImage.getRaster();
			int[] pixelArray = new int[1];
			for (int y = 0; y < data.height; y++) {
				for (int x = 0; x < data.width; x++) {
					int pixel = data.getPixel(x, y);
					pixelArray[0] = pixel;
					raster.setPixel(x, y, pixelArray);
				}
			}
			return bufferedImage;
		}
	}

I to już będzie działać! W drugą stronę, jak już wspominałem, problemu nie ma, bowiem kod jest już przygotowany do konwersji BufferedImage’ów z przezroczystością na Image’e z biblioteki SWT.

Na koniec jeszcze kilka słów o tym, do czego może przydać się taka konwersja. Możliwości SWT pod względem edycji grafiki są bardzo, bardzo skromne, dlatego też jeśli wydajność nie jest akurat bardzo ważna (np. w przypadku, gdy taką konwersję wykonujemy raz, przy starcie aplikacji), możemy w taki oto dość prosty sposób przekonwertować obrazek SWT na AWT, wykonać niezbędne operacje – skalowanie, rysowanie, blending, renderowanie tekstu, filtrowanie, i wiele, wiele więcej, co oferuje Java2D, a następnie przekonwertować wynikowy obraz z powrotem na SWT. Ja, dla przykładu używam takiej konwersji, by móc przeskalować obrazek wybrany przez użytkownika z możliwie wysoką jakością – SWT tego zwyczajnie nie umożliwia, a poza tym nie chcę, by program zachowywał się inaczej w zależności od platformy, na której działa (SWT korzysta z natywnych metod operacji na grafice).

Pozdrawiam.

2 komentarze

  1. C

    w C było by 10 razy szybciej i zużywało by 100 razy mniej RAMu.

    Odpowiedz
  2. Mavis Sykes

    Sam przygodę z programowaniem zaczynałem od C , szybko porzuciłem go na rzecz PHP i JS. Po pewnym czasie zainteresowałem się Javą, lecz w żaden sposób nie przemawiał do mnie ten język. Spróbowałem C# i przez kilka tygodni tylko w nim pisałem, olałem php i resztę. ;p Nie wiem właściwie czemu ten język mi się tak spodobał. Przyzwyczajony jestem do prostych języków typu php a tutaj taka zamiana, w dodatku tam jest obiektowe wszystko (Chyba), ale bardzo przyjemnie się w nim pisze. Osobiście polecałbym C# jeżeli platformą ma być Windows, w przeciwnym wypadku (różne platformy) pozostaje tylko Java.Z tymi językami jest o tyle problem, że C# wymaga .NET z tego co pamiętam, z resztą Java też potrzebuje środowiska w którym się uruchomi. PHP? Do takiej aplikacji raczej w żaden sposób się nie nada. ;p C ? Lubisz? Jesteś masochistą? Ma to swoje plusy, kod wykonuje się bardzo szybko.

    Odpowiedz

Zostaw komentarz

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>