Scale picture using LWJGL 3 and modern OpenGL in an SWT context - java

I'm hoping to scale 12MP images from a machine vision camera using LWJGL 3 and an SWT GLCanvas.
Scaling is obviously computationally intensive so I'd like to get the GPU to take care of this for me, but I am very unfamiliar with OpenGL. Further, every example I've looked at for LWJGL appears to be for much older versions of LWJGL or use deprecated methods; it appears LWJGL has undergone radical changes throughout its life.
I've provided a sample class which should describe how I'm desiring to implement this functionality, but I need help filling in the blanks (preferably using modern OpenGL and LWJGL 3):
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.opengl.GLCanvas;
import org.eclipse.swt.opengl.GLData;
import org.eclipse.swt.widgets.Composite;
import org.lwjgl.opengl.GLContext;
public class LiveCameraView extends Composite
{
private GLCanvas canvas;
public LiveCameraView(Composite parent, int style)
{
super(parent, style);
this.setLayout(new FillLayout());
GLData data = new GLData();
data.doubleBuffer = true;
canvas = new GLCanvas(this, SWT.NONE, data);
}
public void updateImage(byte[] bgrPixels, int imageWidth, int imageHeight)
{
canvas.setCurrent();
GLContext.createFromCurrent();
/*
* STEP 1: Translate pixels into a GL texture from the 3-byte BGR byte[]
* buffer.
*/
/*
* STEP 2: Now that the GPU has the full sized image, we'll get the GPU to
* scale the image appropriately.
*/
double scalingFactor = getScalingFactor(imageWidth, imageHeight);
canvas.swapBuffers();
}
private double getScalingFactor(int originalWidth, int originalHeight)
{
int availableWidth = canvas.getBounds().width;
int availableHeight = canvas.getBounds().height;
// We can either scale to the available width or the available height, but
// in order to guarantee that the whole image is visible we choose the
// smaller of the two scaling factors.
double scaleWidth = (double) availableWidth / (double) originalWidth;
double scaleHeight = (double) availableHeight / (double) originalHeight;
double scale = Math.min(scaleWidth, scaleHeight);
return scale;
}
}
In a separate thread new images are being acquired from the camera continuously. Ideally that thread will asynchronously invoke the updateImage(...) method and provide the raw BGR data of the most recent image.
I believe this should be achievable using the outlined paradigm, but I could be way off base. I appreciate any good direction.
As a final note, this question arose from my initial question asked here: My initial question concerning the general paradigm

Related

How can I draw on a video while recording it in android, and save the video and the drawing?

I am trying to develop an app that allows me to draw on a video while recording it, and to then save both the recording and the video in one mp4 file for later use. Also, I want to use the camera2 library, especially that I need my app to run for devices higher than API 21, and I am always avoiding deprecated libraries.
I tried many ways to do it, including FFmpeg in which I placed an overlay of the TextureView.getBitmap() (from the camera) and a bitmap taken from the canvas. It worked but since it is a slow function, the video couldn't catch enough frames (not even 25 fps), and it ran so fast. I want audio to be included as well.
I thought about the MediaProjection library, but I am not sure if it can capture the layout containg the camera and the drawing only inside its VirtualDisplay, because the app user may add text as well on the video, and I don't want the keyboard to appear.
Please help, it's been a week of research and I found nothing that worked fine for me.
P.S: I don't have a problem if a little bit of processing time is included after that the user presses the "Stop Recording"button.
EDITED:
Now after Eddy's Answer, I am using the shadercam app to draw on the camera surface since the app does the video rendering, and the workaround to do is about rendering my canvas into a bitmap then into a GL texture, however I am not being able to do it successfully. I need your help guys, I need to finish the app :S
I am using the shadercam library (https://github.com/googlecreativelab/shadercam), and I replaced the "ExampleRenderer" file with the following code:
public class WriteDrawRenderer extends CameraRenderer
{
private float offsetR = 1f;
private float offsetG = 1f;
private float offsetB = 1f;
private float touchX = 1000000000;
private float touchY = 1000000000;
private Bitmap textBitmap;
private int textureId;
private boolean isFirstTime = true;
//creates a new canvas that will draw into a bitmap instead of rendering into the screen
private Canvas bitmapCanvas;
/**
* By not modifying anything, our default shaders will be used in the assets folder of shadercam.
*
* Base all shaders off those, since there are some default uniforms/textures that will
* be passed every time for the camera coordinates and texture coordinates
*/
public WriteDrawRenderer(Context context, SurfaceTexture previewSurface, int width, int height)
{
super(context, previewSurface, width, height, "touchcolor.frag.glsl", "touchcolor.vert.glsl");
//other setup if need be done here
}
/**
* we override {#link #setUniformsAndAttribs()} and make sure to call the super so we can add
* our own uniforms to our shaders here. CameraRenderer handles the rest for us automatically
*/
#Override
protected void setUniformsAndAttribs()
{
super.setUniformsAndAttribs();
int offsetRLoc = GLES20.glGetUniformLocation(mCameraShaderProgram, "offsetR");
int offsetGLoc = GLES20.glGetUniformLocation(mCameraShaderProgram, "offsetG");
int offsetBLoc = GLES20.glGetUniformLocation(mCameraShaderProgram, "offsetB");
GLES20.glUniform1f(offsetRLoc, offsetR);
GLES20.glUniform1f(offsetGLoc, offsetG);
GLES20.glUniform1f(offsetBLoc, offsetB);
if (touchX < 1000000000 && touchY < 1000000000)
{
//creates a Paint object
Paint yellowPaint = new Paint();
//makes it yellow
yellowPaint.setColor(Color.YELLOW);
//sets the anti-aliasing for texts
yellowPaint.setAntiAlias(true);
yellowPaint.setTextSize(70);
if (isFirstTime)
{
textBitmap = Bitmap.createBitmap(mSurfaceWidth, mSurfaceHeight, Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas(textBitmap);
}
bitmapCanvas.drawText("Test Text", touchX, touchY, yellowPaint);
if (isFirstTime)
{
textureId = addTexture(textBitmap, "textBitmap");
isFirstTime = false;
}
else
{
updateTexture(textureId, textBitmap);
}
touchX = 1000000000;
touchY = 1000000000;
}
}
/**
* take touch points on that textureview and turn them into multipliers for the color channels
* of our shader, simple, yet effective way to illustrate how easy it is to integrate app
* interaction into our glsl shaders
* #param rawX raw x on screen
* #param rawY raw y on screen
*/
public void setTouchPoint(float rawX, float rawY)
{
this.touchX = rawX;
this.touchY = rawY;
}
}
Please help guys, it's been a month and I am still stuck with the same app :( and have no idea about opengl. Two weeks and I'm trying to use this project for my app, and nothing is being rendered on the video.
Thanks in advance!
Here's a rough outline that should work, but it's quite a bit of work:
Set up a android.media.MediaRecorder for recording the video and audio
Get a Surface from MediaRecorder and set up an EGLImage from it (https://developer.android.com/reference/android/opengl/EGL14.html#eglCreateWindowSurface(android.opengl.EGLDisplay, android.opengl.EGLConfig, java.lang.Object, int[], int) ); you'll need a whole OpenGL context and setup for this. Then you'll need to set that EGLImage as your render target.
Create a SurfaceTexture within that GL context.
Configure camera to send data to that SurfaceTexture
Start the MediaRecorder
On each frame received from camera, convert the drawing done by the user to a GL texture, and composite the camera texture and the user drawing.
Finally, call glSwapBuffers to send the composited frame to the video recorder

LibGDX: Use same TextureAtlas twice or use two different TextureAtlases

I'm migrating my native Android game to libGDX. That's why I use flipped graphics. Apparently NinePatches can't be flipped. (They are invisible or look strange.)
What would be more efficient:
use one big TextureAtlas containing all graphic files and load it twice (flipped and unflipped) or
use one big TextureAtlas for the flipped graphic files and a second small one for the NinePatch graphics?
Type A:
public static TextureAtlas atlas, atlas2;
public static void load() {
// big atlas (1024 x 1024)
atlas = new TextureAtlas(Gdx.files.internal("game.atlas"), true);
// find many AtlasRegions here
// Same TextureAtlas. Loaded into memory twice?
atlas2 = new TextureAtlas(Gdx.files.internal("game.atlas"), false);
button = ninepatch.createPatch("button");
dialog = ninepatch.createPatch("dialog");
}
Type B:
public static TextureAtlas atlas, ninepatch;
public static void load() {
// big atlas (1024 x 1024)
atlas = new TextureAtlas(Gdx.files.internal("game.atlas"), true);
// find many AtlasRegions here
// small atlas (128 x 64)
ninepatch = new TextureAtlas(Gdx.files.internal("ninepatch.atlas"), false);
button = ninepatch.createPatch("button");
dialog = ninepatch.createPatch("dialog");
}
now I do not have time to test, I expound the idea, but I think it can work, is based on a texture, not texture atlas for simplicity.
short metadata = 2;
Texture yourTextureMetadata = new Texture(Gdx.files.internal(metaDataTexture));
int width = yourTextureMetadata.getWidth() - metadata;
int height = yourTextureMetadata.getHeight() - metadata;
TextureRegion yourTextureClean = new TextureRegion(yourTextureMetadata,
1, 1, width, height);
I assume that metadata has a size of two, now I do not remember, sorry
the idea would take, the largest texture, with metadata and then cut them to have clean on the other side so that you can turn around, I hope to work.
for texturealtas would similar, findRegions and cut metadata and save them without metadata
On the other hand, note that you have static textures, and I think when android change of context, your application to another application, and then back to yours application, you can give errors visualization, your images can black display

How to change point size or shape in FastScatterPlot (JFreeChart)?

I have a large collection of 2D coordinates (i.e., easily in the order of 100k to 200k x,y pairs) that I would like to visualize as a scatter plot. For the application that this is intended for, having so many points makes sense and I can't/won't reduce the number for different reasons. To plot this in Java, I use JFreeChart. I have played around with ChartFactory.createScatterPlot() and 50k randomly generated points, and while this gives the greatest amount of flexibility to set the appearance (point size/color/shape), it is slow for displaying many points. That means, it takes some time to appear and zooming is also delayed/not smooth. However, once only few points are actually visible, i.e., deeply zoomed in, the visualization is nicely responsive.
On the contrary, FastScatterPlot() allows to easily draw 500k randomly generated 2D points but the appearance is not really nice as I managed only to set the color so far (using, e.g., setPaint(Color.BLUE)) but not the shape or the size. The size is a problem in particular as the individual points are really small.
How can I change the point size or shape in FastScatterPlot?
And related to this, is there a way to make the chart returned by ChartFactory.createScatterPlot() more responsive?
My data is fixed and the rendering must principally not necessarily change during runtime hence if there are ways to disconnect any listeners or such to improve performance, this would also be an option.
Thanks in advance.
Thanks to the hint by trashgod, I decided to simply extend FastScatterPlot in the following way. You can now specify the colors, sizes and shapes in the constructor of ExtendedFastScatterPlot and it works very nicely for 500k points. Using fillOval() for half of the points seems to slow down things when compared to using fillRect() for all the points but that is not really an issue as long as the plot is overall acceptably responsive. And it is now much more responsive compared to XYPlot. Little effort, great value!
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.geom.Rectangle2D;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CrosshairState;
import org.jfree.chart.plot.FastScatterPlot;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.ui.RectangleEdge;
public class ExtendedFastScatterPlot extends FastScatterPlot {
/**
*
*/
private static final long serialVersionUID = 1L;
int[] sizes;
Paint[] colors;
int[] shapes;
public ExtendedFastScatterPlot(float[][] data, NumberAxis domainAxis, NumberAxis rangeAxis, int[] sizes, Paint[] colors, int[] shapes) {
super(data,domainAxis,rangeAxis);
this.sizes = sizes;
this.colors = colors;
this.shapes = shapes;
}
#Override
public void render(Graphics2D g2, Rectangle2D dataArea, PlotRenderingInfo info, CrosshairState crosshairState) {
//g2.setPaint(Color.BLUE);
if (this.getData() != null) {
for (int i = 0; i < this.getData()[0].length; i++) {
float x = this.getData()[0][i];
float y = this.getData()[1][i];
int size = this.sizes[i];
int transX = (int) this.getDomainAxis().valueToJava2D(x, dataArea, RectangleEdge.BOTTOM);
int transY = (int) this.getRangeAxis().valueToJava2D(y, dataArea, RectangleEdge.LEFT);
g2.setPaint(this.colors[i]);
if( 1 == this.shapes[i])
{
g2.fillRect(transX, transY, size, size);
}
else
{
g2.fillOval(transX, transY, size, size);
}
}
}
}
}
For speed, you might try the alternate FastScatterPlot calculation suggested in the render() method's source comments; profile to compare. For size, you can change the rendered size in the same method; the following would quadruple the size of each point.
g2.fillRect(transX, transY, 2, 2);

Java - resize image without losing quality

I have 10,000 photos that need to be resized so I have a Java program to do that. Unfortunately, the quality of the image is poorly lost and I don't have access to the uncompressed images.
import java.awt.Graphics;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
/**
* This class will resize all the images in a given folder
* #author
*
*/
public class JavaImageResizer {
public static void main(String[] args) throws IOException {
File folder = new File("/Users/me/Desktop/images/");
File[] listOfFiles = folder.listFiles();
System.out.println("Total No of Files:"+listOfFiles.length);
BufferedImage img = null;
BufferedImage tempPNG = null;
BufferedImage tempJPG = null;
File newFilePNG = null;
File newFileJPG = null;
for (int i = 0; i < listOfFiles.length; i++) {
if (listOfFiles[i].isFile()) {
System.out.println("File " + listOfFiles[i].getName());
img = ImageIO.read(new File("/Users/me/Desktop/images/"+listOfFiles[i].getName()));
tempJPG = resizeImage(img, img.getWidth(), img.getHeight());
newFileJPG = new File("/Users/me/Desktop/images/"+listOfFiles[i].getName()+"_New");
ImageIO.write(tempJPG, "jpg", newFileJPG);
}
}
System.out.println("DONE");
}
/**
* This function resize the image file and returns the BufferedImage object that can be saved to file system.
*/
public static BufferedImage resizeImage(final Image image, int width, int height) {
int targetw = 0;
int targeth = 75;
if (width > height)targetw = 112;
else targetw = 50;
do {
if (width > targetw) {
width /= 2;
if (width < targetw) width = targetw;
}
if (height > targeth) {
height /= 2;
if (height < targeth) height = targeth;
}
} while (width != targetw || height != targeth);
final BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
final Graphics2D graphics2D = bufferedImage.createGraphics();
graphics2D.setComposite(AlphaComposite.Src);
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.drawImage(image, 0, 0, width, height, null);
graphics2D.dispose();
return bufferedImage;
}
An image I am working with is this:
This is the manual resizing I've done in Microsoft Paint:
and this is the output from my program [bilinear]:
UPDATE: No significant difference using BICUBIC
and this is the output from my program [bicubic]:
is there anyway to increase the quality of the program output so I don't have to manually resize all photos?
Thank you in advance!
Unfortunately, there is no recommended out-of-the-box scaling in Java that provides visually good results. Among others, here are the methods I recommend for scaling:
Lanczos3 Resampling (usually visually better, but slower)
Progressive Down Scaling (usually visually fine, can be quite fast)
One-Step scaling for up scaling (with Graphics2d bicubic fast and good results, usually not as good as Lanczos3)
Examples for every method can be found in this answer.
Visual Comparison
Here is your image scaled to 96x140 with different methods/libs. Click on the image to get the full size:
Morten Nobel's lib Lanczos3
Thumbnailator Bilinear Progressive Scaling
Imgscalr ULTRA_QUALTY (1/7 step Bicubic Progressive Scaling)
Imgscalr QUALTY (1/2 step Bicubic Progressive Scaling)
Morten Nobel's lib Bilinear Progressive Scaling
Graphics2d Bicubic interpolation
Graphics2d Nearest Neighbor interpolation
Photoshop CS5 bicubic as reference
Unfortunately a single image is not enough to judge a scaling algorithm, you should test icons with sharp edges, photos with text, etc.
Lanczos Resampling
Is said to be good for up- and especially downscaling. Unfortunately there is no native implementation in current JDK so you either implement it yourself and use a lib like Morten Nobel's lib. A simple example using said lib:
ResampleOp resizeOp = new ResampleOp(dWidth, dHeight);
resizeOp.setFilter(ResampleFilters.getLanczos3Filter());
BufferedImage scaledImage = resizeOp.filter(imageToScale, null);
The lib is published on maven-central which is not mentioned unfortunately. The downside is that it usually is very slow without any highly optimized or hardware accelerated implementations known to me. Nobel's implementation is about 8 times slower than a 1/2 step progressive scaling algorithm with Graphics2d. Read more about this lib on his blog.
Progressive Scaling
Mentioned in Chris Campbell's blog about scaling in Java, progressive scaling is basically incrementally scaling an image in smaller steps until the final dimensions are reached. Campbell describes it as halving width/height until you reach target. This produces good results and can be used with Graphics2D which can be hardware accelerated, therefore usually having very good performance with acceptable results in most cases. The major downside of this is if downscaled less than half using Graphics2D provides the same mediocre results since it is only scaled once.
Here is a simple example on how it works:
The following libs incorporate forms of progressive scaling based on Graphics2d:
Thumbnailator v0.4.8
Uses the progressive bilinear algorithm if the target is at least half of every dimension, otherwise it uses simple Graphics2d bilinear scaling and bicubic for upscaling.
Resizer resizer = DefaultResizerFactory.getInstance().getResizer(
new Dimension(imageToScale.getWidth(), imageToScale.getHeight()),
new Dimension(dWidth, dHeight))
BufferedImage scaledImage = new FixedSizeThumbnailMaker(
dWidth, dHeight, false, true).resizer(resizer).make(imageToScale);
It is as fast or slightly faster than one-step scaling with Graphics2d scoring an average of 6.9 sec in my benchmark.
Imgscalr v4.2
Uses progressive bicubic scaling. In the QUALITY setting it uses Campbell style algorithm with halving the dimensions every step while the ULTRA_QUALITY has finer steps, reducing the size every increment by 1/7 which generates generally softer images but minimizes the instances where only 1 iteration is used.
BufferedImage scaledImage = Scalr.resize(imageToScale, Scalr.Method.ULTRA_QUALITY, Scalr.Mode.FIT_EXACT, dWidth, dHeight, bufferedImageOpArray);
The major downside is performance. ULTRA_QUALITY is considerably slower than the other libs. Even QUALITY a bit slower than Thumbnailator's implementation. My simple benchmark resulted in 26.2 sec and 11.1 sec average respectively.
Morten Nobel's lib v0.8.6
Has also implementations for progressive scaling for all basic Graphics2d (bilinear, bicubic & nearest neighbor)
BufferedImage scaledImage = new MultiStepRescaleOp(dWidth, dHeight, RenderingHints.VALUE_INTERPOLATION_BILINEAR).filter(imageToScale, null);
A word on JDK Scaling Methods
Current jdk way to scale an image would be something like this
scaledImage = new BufferedImage(dWidth, dHeight, imageType);
Graphics2D graphics2D = scaledImage.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
graphics2D.dispose();
but most are very disappointed with the result of downscaling no matter what interpolation or other RenderHints are used. On the other hand upscaling seems to produce acceptable images (best would be bicubic). In previous JDK version (we talking 90s v1.1) Image.getScaledInstance() was introduced which provided good visual results with parameter SCALE_AREA_AVERAGING but you are discouraged to use it - read the full explanation here.
Thumbnailator is a library that was written to create high-quality thumbnails in a simple manner, and doing a batch conversion of existing images is one of its use cases.
Performing batch resizing
For example, to adapt your example using Thumbnailator, you should be able to achieve similar results with the following code:
File folder = new File("/Users/me/Desktop/images/");
Thumbnails.of(folder.listFiles())
.size(112, 75)
.outputFormat("jpg")
.toFiles(Rename.PREFIX_DOT_THUMBNAIL);
This will go ahead and takes all files in your images directory and proceed to process them one by one, try to resize them to fit in the dimensions of 112 x 75, and it will attempt to preserve the aspect ratio of the original image to prevent "warping" of the image.
Thumbnailator will go ahead and read all files, regardless of image types (as long as the Java Image IO supports the format, Thumbnailator will process it), perform the resizing operation and output the thumbnails as JPEG files, while tacking on a thumbnail. to the beginning of the file name.
The following is an illustration of how the file name of the original will be used in the file name of the thumbnail if the above code is executed.
images/fireworks.jpg -> images/thumbnail.fireworks.jpg
images/illustration.png -> images/thumbnail.illustration.png
images/mountains.jpg -> images/thumbnail.mountains.jpg
Generating high-quality thumbnails
In terms of image quality, as mentioned in Marco13's answer, the technique described by Chris Campbell in his The Perils of Image.getScaledInstance() is implemented in Thumbnailator, resulting in high-quality thumbnails without requiring any complicated processing.
The following is the thumbnail generated when resizing the fireworks image shown in the original question using Thumbnailator:
The above image was created with the following code:
BufferedImage thumbnail =
Thumbnails.of(new URL("http://i.stack.imgur.com/X0aPT.jpg"))
.height(75)
.asBufferedImage();
ImageIO.write(thumbnail, "png", new File("24745147.png"));
The code shows that it can also accept URLs as input, and that Thumbnailator is also capable of creating BufferedImages as well.
Disclaimer: I am the maintainer of the Thumbnailator library.
Given your input image, the method from the answer in the first link in the comments (kudos to Chris Campbell) produces one of the following thumbnails:
(The other one is the thumbnail that you created with MS Paint. It's hard to call one of them "better" than the other...)
EDIT: Just to point this out as well: The main problem with your original code was that you did not really scale the image in multiple steps. You just used a strange loop to "compute" the target size. The key point is that you actually perform the scaling in multiple steps.
Just for completeness, the MVCE
(Edit: I mentioned Chris Campbell and referred to the source via the comments, but to make this more clear here: The following is based on the article The Perils of Image.getScaledInstance() )
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;
public class ResizeQuality
{
public static void main(String[] args) throws IOException
{
BufferedImage image = ImageIO.read(new File("X0aPT.jpg"));
BufferedImage scaled = getScaledInstance(
image, 51, 75, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
writeJPG(scaled, new FileOutputStream("X0aPT_tn.jpg"), 0.85f);
}
public static BufferedImage getScaledInstance(
BufferedImage img, int targetWidth,
int targetHeight, Object hint,
boolean higherQuality)
{
int type =
(img.getTransparency() == Transparency.OPAQUE)
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
int w, h;
if (higherQuality)
{
// Use multi-step technique: start with original size, then
// scale down in multiple passes with drawImage()
// until the target size is reached
w = img.getWidth();
h = img.getHeight();
}
else
{
// Use one-step technique: scale directly from original
// size to target size with a single drawImage() call
w = targetWidth;
h = targetHeight;
}
do
{
if (higherQuality && w > targetWidth)
{
w /= 2;
if (w < targetWidth)
{
w = targetWidth;
}
}
if (higherQuality && h > targetHeight)
{
h /= 2;
if (h < targetHeight)
{
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(w, h, type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
return ret;
}
public static void writeJPG(
BufferedImage bufferedImage,
OutputStream outputStream,
float quality) throws IOException
{
Iterator<ImageWriter> iterator =
ImageIO.getImageWritersByFormatName("jpg");
ImageWriter imageWriter = iterator.next();
ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam();
imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
imageWriteParam.setCompressionQuality(quality);
ImageOutputStream imageOutputStream =
new MemoryCacheImageOutputStream(outputStream);
imageWriter.setOutput(imageOutputStream);
IIOImage iioimage = new IIOImage(bufferedImage, null, null);
imageWriter.write(null, iioimage, imageWriteParam);
imageOutputStream.flush();
}
}
After days of research i would prefer javaxt.
use Thejavaxt.io.Image class has a constructor like:
public Image(java.awt.image.BufferedImage bufferedImage)
so you can do (another example):
javaxt.io.Image image = new javaxt.io.Image(bufferedImage);
image.setWidth(50);
image.setOutputQuality(1);
Here's the output:
We should not forget a TwelveMonkeys Library
It contains a really impressive filter collection.
Usage example:
BufferedImage input = ...; // Image to resample
int width, height = ...; // new width/height
BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS);
BufferedImage output = resampler.filter(input, null);
Below are my own implementation of Progressive Scaling, without using any external library. Hope this help.
private static BufferedImage progressiveScaling(BufferedImage before, Integer longestSideLength) {
if (before != null) {
Integer w = before.getWidth();
Integer h = before.getHeight();
Double ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;
//Multi Step Rescale operation
//This technique is describen in Chris Campbellā€™s blog The Perils of Image.getScaledInstance(). As Chris mentions, when downscaling to something less than factor 0.5, you get the best result by doing multiple downscaling with a minimum factor of 0.5 (in other words: each scaling operation should scale to maximum half the size).
while (ratio < 0.5) {
BufferedImage tmp = scale(before, 0.5);
before = tmp;
w = before.getWidth();
h = before.getHeight();
ratio = h > w ? longestSideLength.doubleValue() / h : longestSideLength.doubleValue() / w;
}
BufferedImage after = scale(before, ratio);
return after;
}
return null;
}
private static BufferedImage scale(BufferedImage imageToScale, Double ratio) {
Integer dWidth = ((Double) (imageToScale.getWidth() * ratio)).intValue();
Integer dHeight = ((Double) (imageToScale.getHeight() * ratio)).intValue();
BufferedImage scaledImage = new BufferedImage(dWidth, dHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = scaledImage.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.drawImage(imageToScale, 0, 0, dWidth, dHeight, null);
graphics2D.dispose();
return scaledImage;
}
The result seems to be better (than the result of your program), if you apply Gaussian blur before resizing:
This is the result I get, with sigma * (scale factor) = 0.3:
With ImageJ the code to do this is quite short:
import ij.IJ;
import ij.ImagePlus;
import ij.io.Opener;
import ij.process.ImageProcessor;
public class Resizer {
public static void main(String[] args) {
processPicture("X0aPT.jpg", "output.jpg", 0.0198, ImageProcessor.NONE, 0.3);
}
public static void processPicture(String inputFile, String outputFilePath, double scaleFactor, int interpolationMethod, double sigmaFactor) {
Opener opener = new Opener();
ImageProcessor ip = opener.openImage(inputFile).getProcessor();
ip.blurGaussian(sigmaFactor / scaleFactor);
ip.setInterpolationMethod(interpolationMethod);
ImageProcessor outputProcessor = ip.resize((int)(ip.getWidth() * scaleFactor), (int)(ip.getHeight()*scaleFactor));
IJ.saveAs(new ImagePlus("", outputProcessor), outputFilePath.substring(outputFilePath.lastIndexOf('.')+1), outputFilePath);
}
}
BTW: You only need ij-1.49d.jar (or equivalent for other version); there's no need to install ImageJ.

Performance with Manually Scaled Bitmaps across multiple devices

I am working on an Android game that is pretty heavy in terms of image resources. One of the challenges that I am facing is that I need to manually scale the graphics to match the different screen sizes. The reason for not allowing Android to auto-scale is that the game is dependent upon the graphics not getting stretched or skewed. I will be potentially switching in 3-4 bitmaps per screen press, so performance is key (I already have a caching operation set up to help w/ this).
After considering the problem and the diversity of Android devices out there, I decided on the following approach to target as many devices as possible:
Make density-specific graphics (via http://developer.android.com/guide/topics/resources/providing-resources.html and the other suggestions listed in dev.android.com )
Rather than making many versions of an image for the different screen sizes, create one very large image that covers all target screens and use manual scaling to reduce its size to match the device.
So far, this approach is going well, but I need to squeeze more performance out of my custom BitmapScaler class. I have leveraged the code base found here to suit my own needs: http://zerocredibility.wordpress.com/2011/01/27/android-bitmap-scaling/ .
With that being said, here are my questions: Does anyone have any comments on the viability of my approach for solving the screen destiny / screen size problem? I am aware that I am trading performance for convenience here. Can anyone suggest a way to squeeze out better performance in my bitmap scaling operation (Below)?
public class BitmapScaler {
private Bitmap scaledBitmap;
public Bitmap getScaledBitmap()
{
return scaledBitmap;
}
/* IMPORANT NOTES:
* The process of scaling bitmaps for Android is not a straightforward process. In order to
* help preserve memory on the phone, you need to go through several steps:
*
* 1. Decode the target resource, but use the inJustDecodeBounds option. This allows you to "sample"
* the resource without actually incurring the hit of loading the entire resource. If set to true, the decoder will return null
* (no bitmap), but the out... fields will still be set, allowing the caller to query the bitmap without having to allocate
* the memory for its pixels.
* 2. Determine the new aspects of your bitmap, particularly the scale and sample. If the sample size is set to a value > 1,
* requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number
* of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns
* an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1.
* Note: the decoder will try to fulfill this request, but the resulting bitmap may have different dimensions that precisely what
* has been requested. Also, powers of 2 are often faster/easier for the decoder to honor.
* 3. Prescale the bitmap as much as possible, rather than trying to fully decode it in memory. This is a less
* expensive operation and allows you to "right size" your image.
* 4. Create your new bitmap, applying a matrix to "fine tune" the final resize.
*
* Partial Ref: http://zerocredibility.wordpress.com/2011/01/27/android-bitmap-scaling/
*/
public BitmapScaler(Resources resources, int targetResourceID, int targetWidth, int targetHeight)
{
BitmapInfo originalInfo = getOriginalBitmapInfo(resources, targetResourceID);
BitmapInfo newInfo = getScaledBitmapInfo(targetHeight, targetWidth, originalInfo);
prescaleScaledBitmap(resources, targetResourceID, newInfo);
scaleScaledBitmap(newInfo);
}
private void scaleScaledBitmap(BitmapInfo newInfo)
{
int ScaledHeight = scaledBitmap.getHeight();
int ScaledWidth = scaledBitmap.getWidth();
float MatrixWidth = ((float)newInfo.width) / ScaledWidth;
float MatrixHeight = ((float)newInfo.height) / ScaledHeight;
Matrix matrix = new Matrix();
matrix.postScale(MatrixWidth, MatrixHeight);
scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, ScaledWidth, ScaledHeight, matrix, true);
}
private void prescaleScaledBitmap(Resources resources, int targetResourceID, BitmapInfo newInfo)
{
BitmapFactory.Options scaledOpts = new BitmapFactory.Options();
scaledOpts.inSampleSize = newInfo.sample;
scaledBitmap = BitmapFactory.decodeResource(resources, targetResourceID, scaledOpts);
}
private BitmapInfo getOriginalBitmapInfo(Resources resources, int targetResourceID)
{
BitmapFactory.Options bitOptions = new BitmapFactory.Options();
bitOptions.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, targetResourceID, bitOptions);
return new BitmapInfo(bitOptions.outHeight,bitOptions.outWidth);
}
private BitmapInfo getScaledBitmapInfo(int targetHeight, int targetWidth, BitmapInfo originalBitmapInfo)
{
float HeightRatio = targetHeight / (float)originalBitmapInfo.height;
float WidthRatio = targetWidth / (float)originalBitmapInfo.width;
BitmapInfo newInfo = new BitmapInfo(0,0);
if (HeightRatio > WidthRatio)
{
newInfo.scale = WidthRatio;
newInfo.width = targetWidth;
newInfo.height = (int)(newInfo.scale * originalBitmapInfo.height);
} else {
newInfo.scale = HeightRatio;
newInfo.height = targetHeight;
newInfo.width = (int)(newInfo.scale * originalBitmapInfo.width);
}
newInfo.sample = 1;
int SampleHeight = originalBitmapInfo.height;
int SampleWidth = originalBitmapInfo.width;
while (true) {
if (SampleWidth / 2 < newInfo.width || SampleHeight / 2 < newInfo.height) {
break;
}
SampleWidth /= 2;
SampleHeight /= 2;
newInfo.sample *= 2;
}
return newInfo;
}
}
Thanks!

Categories

Resources