Szybkie Å‚adowanie i rysowanie danych RGB w BufferedImage
W jakimś kodzie Javy działającym pod Windows, odczytuję z dysku kilka dużych bloków danych RGB i chcę to jak najszybciej wyświetlić na ekranie. Dane RGB to 8 bitów na kanał bez żadnej Alfy. Obecnie mam kod podobny do poniższego, aby utworzyć BufferedImage.
BufferedImage getBufferedImage(File file, int width, int height) {
byte[] rgbData = readRGBFromFile(file);
WritableRaster raster = Raster.createInterleavedRaster(
rgbData, width, height,
width * 3, // scanlineStride
3, // pixelStride
new int[]{0, 1, 2}, // bandOffsets
null);
ColorModel colorModel = new ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
new int[]{8, 8, 8}, // bits
false, // hasAlpha
false, // isPreMultiplied
ComponentColorModel.OPAQUE,
DataBuffer.TYPE_BYTE);
return new BufferedImage(colorModel, raster, false, null);
}
Problem polega na tym, że wydajność renderowania tego na ekranie jest dość powolna. Około 250-300 ms. czytałem, że aby uzyskać najlepszą wydajność trzeba wyświetlać w buforze, który jest kompatybilny z ekranem. Aby to zrobić, przekazuję buforowany obraz zwrócony z powyższej metody do metody takiej jak ta.
BufferedImage createCompatibleImage(BufferedImage image)
{
GraphicsConfiguration gc = GraphicsEnvironment.
getLocalGraphicsEnvironment().
getDefaultScreenDevice().
getDefaultConfiguration();
BufferedImage newImage = gc.createCompatibleImage(
image.getWidth(),
image.getHeight(),
Transparency.TRANSLUCENT);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
Ta metoda zasadniczo konwertuje go z RGB NA ARGB w systemie Windows i naprawdę przyspiesza wyświetlanie, ale ta metoda zajmuje ~300 ms dla bloku danych 1600 x 1200 RGB. Więc teraz w zasadzie zamieniłem wydajność problemu z rysowaniem na problem z konwersją.
300ms to mniej więcej tyle samo czasu, ile potrzeba do załadowania danych RGB z dysku. Ja bym myślę, że mógłbym zrobić coś szybciej.
Czy jest lepszy sposób na konwersję? A może pomogłoby, gdybym wcześniej zmodyfikował dane RGB i sam dodał kanał alfa? Jeśli tak, jak wyglądałby mój Raster i ColorModel. Ponadto, ponieważ moje dane RGB nie zawierają przezroczystości, Czy Mogę uzyskać jakieś ulepszenia wydajności za pomocą wstępnie zwielokrotnionej Alfy lub czegoś takiego? Przepraszam, trochę się pogubiłem w tym Colormodelu, rastrze. Dzięki!2 answers
Zdaję sobie sprawę, że to naprawdę stare pytanie, po prostu zamieszczam to dla każdego, kto może natknąć się na to pytanie, szukając więcej opcji. Ostatnio miałem problem, w którym próbowałem pobrać duży (720p) bajt RGB [] i wyrenderować go do BufferedImage
. Oryginalna implementacja, której używałem, wyglądała mniej więcej tak (uproszczona tutaj):
public void processFrame(byte[] frame, int width, int height)
{
DataBuffer videoBuffer = new DataBufferByte(frame,frame.length);
BufferedImage currentImage = new BufferedImage(width,height,BufferedImage.TYPE_3BYTE_BGR);
ComponentSampleModel sampleModel = new ComponentSampleModel(DataBuffer.TYPE_BYTE,width,height,3,width*3,new int[] {2,1,0});
Raster raster = Raster.createRaster(sampleModel,videoBuffer,null);
currentImage.setData(raster);
}
Nawet z optymalizacjami, takimi jak tworzenie BufferedImage
i ComponentSampleModel
raz i ponowne ich użycie, ostatni krok wywołania {[5] } na BufferedImage
był nadal przyjmowanie rzędu 50-60 milisekund, co jest niedopuszczalne.
W końcu zdałem sobie sprawę, że przynajmniej w moim scenariuszu można zapisać bezpośrednio do tablicy bajtów nośnych BufferedImage
i ominąć większość przetwarzania pośredniego (zakładając, że metadane nośników dla obrazu są już poprawne). Więc zmieniłem mój kod, aby wyglądał tak:
public void processFrame(byte[] frame, int width, int height)
{
BufferedImage currentImage = new BufferedImage(width,height,BufferedImage.TYPE_3BYTE_BGR);
byte[] imgData = ((DataBufferByte)currentImage.getRaster().getDataBuffer()).getData();
System.arraycopy(frame,0,imgData,0,frame.length);
}
Po prostu robiąc to, moja wydajność poprawiła się o około 20 razy. Teraz przetwarzam te same klatki W 3-5 milisekund zamiast 50-60 milisekund.
Może to nie dotyczy wszystkich przypadków, ale pomyślałem, że podzielę się, gdyby ktoś inny uznał to za przydatne.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-21 20:25:11
Po zabawie z tym mam przyzwoitą odpowiedź, która działa na Windows, Jeśli bieżąca konfiguracja Grafiki jest za pomocą ARGB integer packed rasters.
Najpierw tworzę kompatybilny BufferedImage, a następnie ręcznie konwertuję moją tablicę bajtów RGB na tablicę ARGB int. Następnie dostaję Raster z kompatybilnego BufferedImage i zapisuję do niego moje ARGB ints. To jest o wiele szybsze.
Mam również klasę, która sprawdza, czy zgodny BufferedImage jest w formacie I spodziewaj się, jeśli nie jest to domyślnie starsze wolniejsze podejście.
Oto Klasa. Mam nadzieję, że ci to pomoże.
/**
* This class can read chunks of RGB image data out of a file and return a BufferedImage.
* It may use an optimized technique for loading images that relies on assumptions about the
* default image format on Windows.
*/
public class RGBImageLoader
{
private byte[] tempBuffer_;
private boolean fastLoading_;
public RGBImageLoader()
{
fastLoading_ = canUseFastLoadingTechnique();
}
private boolean canUseFastLoadingTechnique()
{
// Create an image that's compatible with the screen
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BufferedImage image = gc.createCompatibleImage(100, 100, Transparency.TRANSLUCENT);
// On windows this should be an ARGB integer packed raster. If it is then we can
// use our optimization technique
if(image.getType() != BufferedImage.TYPE_INT_ARGB)
return false;
WritableRaster raster = image.getRaster();
if(!(raster instanceof IntegerInterleavedRaster))
return false;
if(!(raster.getDataBuffer() instanceof DataBufferInt))
return false;
if(!(image.getColorModel() instanceof DirectColorModel))
return false;
DirectColorModel colorModel = (DirectColorModel) image.getColorModel();
if(!(colorModel.getColorSpace() instanceof ICC_ColorSpace) ||
colorModel.getNumComponents() != 4 ||
colorModel.getAlphaMask() != 0xff000000 ||
colorModel.getRedMask() != 0xff0000 ||
colorModel.getGreenMask() != 0xff00 ||
colorModel.getBlueMask() != 0xff)
return false;
if(raster.getNumBands() != 4 ||
raster.getNumDataElements() != 1 ||
!(raster.getSampleModel() instanceof SinglePixelPackedSampleModel))
return false;
return true;
}
public BufferedImage loadImage(File file, int width, int height, long imageOffset) throws IOException
{
if(fastLoading_)
return loadImageUsingFastTechnique(file, width, height, imageOffset);
else
return loadImageUsingCompatibleTechnique(file, width, height, imageOffset);
}
private BufferedImage loadImageUsingFastTechnique(File file, int width, int height, long imageOffset) throws IOException
{
int sizeBytes = width * height * 3;
// Make sure buffer is big enough
if(tempBuffer_ == null || tempBuffer_.length < sizeBytes)
tempBuffer_ = new byte[sizeBytes];
RandomAccessFile raf = null;
try
{
raf = new RandomAccessFile(file, "r");
raf.seek(imageOffset);
int bytesRead = raf.read(tempBuffer_, 0, sizeBytes);
if (bytesRead != sizeBytes)
throw new IOException("Invalid byte count. Should be " + sizeBytes + " not " + bytesRead);
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BufferedImage image = gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
WritableRaster raster = image.getRaster();
DataBufferInt dataBuffer = (DataBufferInt) raster.getDataBuffer();
addAlphaChannel(tempBuffer_, sizeBytes, dataBuffer.getData());
return image;
}
finally
{
try
{
if(raf != null)
raf.close();
}
catch(Exception ex)
{
}
}
}
private BufferedImage loadImageUsingCompatibleTechnique(File file, int width, int height, long imageOffset) throws IOException
{
int sizeBytes = width * height * 3;
RandomAccessFile raf = null;
try
{
raf = new RandomAccessFile(file, "r");
// Lets navigate to the offset
raf.seek(imageOffset);
DataBufferByte dataBuffer = new DataBufferByte(sizeBytes);
byte[] bytes = dataBuffer.getData();
int bytesRead = raf.read(bytes, 0, sizeBytes);
if (bytesRead != sizeBytes)
throw new IOException("Invalid byte count. Should be " + sizeBytes + " not " + bytesRead);
WritableRaster raster = Raster.createInterleavedRaster(dataBuffer, // dataBuffer
width, // width
height, // height
width * 3, // scanlineStride
3, // pixelStride
new int[]{0, 1, 2}, // bandOffsets
null); // location
ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), // ColorSpace
new int[]{8, 8, 8}, // bits
false, // hasAlpha
false, // isPreMultiplied
ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
BufferedImage loadImage = new BufferedImage(colorModel, raster, false, null);
// Convert it into a buffered image that's compatible with the current screen.
// Not ideal creating this image twice....
BufferedImage image = createCompatibleImage(loadImage);
return image;
}
finally
{
try
{
if(raf != null)
raf.close();
}
catch(Exception ex)
{
}
}
}
private BufferedImage createCompatibleImage(BufferedImage image)
{
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BufferedImage newImage = gc.createCompatibleImage(image.getWidth(), image.getHeight(), Transparency.TRANSLUCENT);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
private void addAlphaChannel(byte[] rgbBytes, int bytesLen, int[] argbInts)
{
for(int i=0, j=0; i<bytesLen; i+=3, j++)
{
argbInts[j] = ((byte) 0xff) << 24 | // Alpha
(rgbBytes[i] << 16) & (0xff0000) | // Red
(rgbBytes[i+1] << 8) & (0xff00) | // Green
(rgbBytes[i+2]) & (0xff); // Blue
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-13 15:57:28