/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Icy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Icy. If not, see <http://www.gnu.org/licenses/>.
*/
package icy.image;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import icy.image.lut.LUT;
import icy.math.Scaler;
import icy.system.SystemUtil;
import icy.system.thread.Processor;
import icy.system.thread.ThreadUtil;
/**
* @author Stephane
*/
class ARGBImageBuilder
{
private static final int BLOC_SIZE = 512 * 512;
class BlockBuilder implements Runnable
{
/**
* cached variables
*/
private IcyBufferedImage image;
private LUT lut;
private int dest[];
private int offset;
private int length;
private int numChannel;
BlockBuilder(IcyBufferedImage image, LUT lut, int[] dest, int offset, int length)
{
super();
this.image = image;
// use internal lut if specified lut is null
if (lut == null)
this.lut = image.createCompatibleLUT(false);
else
this.lut = lut;
this.dest = dest;
this.offset = offset;
this.length = length;
numChannel = image.getSizeC();
if (this.lut.getNumChannel() != numChannel)
throw new IllegalArgumentException("ARGBImageBuilder.prepare(...): LUT.numChannel != IMAGE.numChannel");
}
@Override
public void run()
{
int[][] componentValues = null;
try
{
// get working buffer
componentValues = requestBuffer(numChannel);
if (componentValues != null)
{
// update output image buffer
final Scaler[] scalers = lut.getScalers();
final boolean signed = image.getIcyColorModel().getDataType_().isSigned();
// scale component values
for (int comp = 0; comp < numChannel; comp++)
scalers[comp].scale(image.getDataXY(comp), offset, componentValues[comp], 0, length, signed);
// build ARGB destination buffer
lut.getColorSpace().fillARGBBuffer(componentValues, dest, offset, length);
}
}
catch (Exception e)
{
// we just ignore any exceptions here as we can be in asynch process
}
finally
{
releaseBuffer(componentValues);
}
}
}
// processor
private final Processor processor;
// data buffer pool
private final List<int[][]> buffers;
/**
*
*/
public ARGBImageBuilder()
{
super();
if (SystemUtil.is32bits())
processor = new Processor(Math.max(1, Math.min(SystemUtil.getNumberOfCPUs() - 1, 4)));
else
processor = new Processor(Math.max(1, Math.min(SystemUtil.getNumberOfCPUs() - 1, 16)));
processor.setThreadName("ARGB Image builder");
processor.setPriority(Processor.NORM_PRIORITY - 1);
buffers = new ArrayList<int[][]>();
}
private static BufferedImage getImage(IcyBufferedImage in, BufferedImage out)
{
if ((out != null) && ImageUtil.sameSize(in, out))
return out;
return new BufferedImage(in.getWidth(), in.getHeight(), BufferedImage.TYPE_INT_ARGB);
}
int[][] requestBuffer(int numChannel)
{
if (numChannel <= 0)
return null;
synchronized (buffers)
{
for (int index = buffers.size() - 1; index >= 0; index--)
if (buffers.get(index).length == numChannel)
return buffers.remove(index);
}
// allocate a new one
return new int[numChannel][BLOC_SIZE];
}
void releaseBuffer(int[][] buffer)
{
if (buffer == null)
return;
// --> blocked
synchronized (buffers)
{
buffers.add(buffer);
}
}
/**
* Convert the source {@link IcyBufferedImage} into the destination ARGB
* {@link BufferedImage}<br>
* If <code>out</code> is null then a new ARGB {@link BufferedImage} is returned.<br>
* Note that output {@link BufferedImage} is fixed to ARGB type (TYPE_INT_ARGB) and the image
* cannot be volatile accelerated.
*
* @param image
* source image
* @param lut
* {@link LUT} is used for color calculation (internal lut is used if null).
* @param out
* destination image. Note that we access image data so it can't be volatile anymore
* which may result in slower drawing.
*/
public BufferedImage buildARGBImage(IcyBufferedImage image, LUT lut, BufferedImage out)
{
// planar size
final int imageSize = image.getSizeX() * image.getSizeY();
final int step = imageSize / BLOC_SIZE;
final BufferedImage result = getImage(image, out);
// destination buffer
final int[] dest = ((DataBufferInt) result.getRaster().getDataBuffer()).getData();
final List<Future<?>> futures = new ArrayList<Future<?>>();
int offset = 0;
try
{
for (int i = 0; i < step; i++)
{
// build bloc
futures.add(addBloc(image, lut, dest, offset, BLOC_SIZE));
offset += BLOC_SIZE;
}
// last bloc
if (offset < imageSize)
futures.add(addBloc(image, lut, dest, offset, imageSize - offset));
// wait until image is built
waitCompletion(futures);
}
catch (IllegalArgumentException e)
{
// image has changed in the meantime, just ignore
}
// release working buffer memory
synchronized (buffers)
{
buffers.clear();
}
return result;
}
/**
* Convert the source {@link IcyBufferedImage} into an ARGB BufferedImage.
* Note that output {@link BufferedImage} is not a volatile accelerated image (slower
* drawing).<br>
* Use {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, int, LUT)} instead if you
* want volatile accelerated image.
*
* @param image
* source image
* @param lut
* {@link LUT} is used for color calculation (internal lut is used if null).
*/
public BufferedImage buildARGBImage(IcyBufferedImage image, LUT lut)
{
return buildARGBImage(image, lut, null);
}
private Future<?> addBloc(IcyBufferedImage image, LUT lut, int dest[], int offset, int length)
{
final BlockBuilder builder = new BlockBuilder(image, lut, dest, offset, length);
Future<?> result = processor.submit(builder);
// not accepted ? retry until it is accepted...
while (result == null)
{
// wait a bit
ThreadUtil.sleep(1);
// and retry task submission
result = processor.submit(builder);
}
return result;
}
private void waitCompletion(List<Future<?>> futures)
{
while (!futures.isEmpty())
{
// get last in queue
final Future<?> f = futures.get(futures.size() - 1);
try
{
// wait for it
f.get();
}
catch (ExecutionException e)
{
// warning
System.out.println("ARGBImageBuilder - Warning: " + e);
}
catch (InterruptedException e)
{
// ignore
}
// remove it
futures.remove(f);
}
}
/**
* Returns <code>true</code> if the ARGB builder is processing an image.
*/
public boolean isProcessing()
{
return processor.isProcessing();
}
/**
* wait until all process ended
*/
public void waitCompletion()
{
processor.waitAll();
}
}