Saturday, July 6, 2013

Power of Two image resizer

This java app automatically resizes images to closest power of two making them suitable for OpenGL/GLES. Useful for mobile developers using OpenGLES.


Usage: java -jar ClosestPOTResizer.jar in.png OR in.jpg


Its just 7KB app. Runs on java 1.2 or above. The download package contains source code of the app and inside distributable folder the executable jar is present.

Download: ClosestPOTResizer.7z


Source code:

/* ##################################################### */

package closestpotresizer;

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.io.File;
import java.io.FileInputStream;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.ImageOutputStream;

/**
 *
 * @author Bindesh Kumar Singh
 * @contact bindeshkumarsingh@gmail.com
 * @website http://www.ourinnovativemind.in
 */

public class ClosestPOTResizer {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
//        System.out.println("" + POTResizer.getClosestPOT(500, false));
//        System.out.println("" + POTResizer.getClosestPOT(500, true));
        try {
            if (args.length < 1) {
                System.out.println("Usage: java -jar ClosestPOTResizer.jar \"in.png OR in.jpg\"");
                return;
            }
            POTResizer r = new POTResizer();
            r.resize(args[0]);
        } catch (Exception ex) {
            Logger.getLogger(ClosestPOTResizer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

}

class POTResizer {
   
    public void resize(final String infile) {
        try {
            BufferedImage img = ImageIO.read(new FileInputStream(infile));
           
            // get extension
            int len = infile.length();
            String extension = infile.substring( infile.lastIndexOf('.') + 1, len );
            String outfile = "POT_" + infile;
           
            // get current size
            int w = img.getWidth();
            int h = img.getHeight();

            // closest POT
            int potW = getClosestPOT(w, true);
            int potH = getClosestPOT(h, true);

            // resize to POT
            img = getScaled(img, potW, potH);

            // save resized image
            storeImage(img, new File(outfile), extension, 0.9f);

        } catch (Exception exp) {
            Logger.getLogger(POTResizer.class.getName()).log(Level.SEVERE, null, exp);
        }
    }

    public boolean storeImage(BufferedImage bi, File outputFile, String extension, float quality) {
        // e.g. storeImage( image, new File( "file.png" ), BufferedImageUtil.IMAGETYPE_PNG, 0.8f);
        try {
            //reconstruct folder structure for image file output
            if (outputFile.getParentFile() != null && !outputFile.getParentFile().exists()) {
                outputFile.getParentFile().mkdirs();
            }
            if (outputFile.exists()) {
                outputFile.delete();
            }
            //get image file suffix
            //get registry ImageWriter for specified image suffix
            // BufferedImageUtil.IMAGETYPE_PNG, 0.8f)
            Iterator writers = ImageIO.getImageWritersBySuffix(extension);
            ImageWriter imageWriter = (ImageWriter) writers.next();
            //set image output params
            ImageWriteParam params = new JPEGImageWriteParam(null);
            params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            params.setCompressionQuality(quality);
            params.setProgressiveMode(javax.imageio.ImageWriteParam.MODE_DISABLED);
            params.setDestinationType(new ImageTypeSpecifier(IndexColorModel
                    .getRGBdefault(), IndexColorModel.getRGBdefault()
                    .createCompatibleSampleModel(16, 16)));
            //writer image to file
            ImageOutputStream imageOutputStream = ImageIO
                    .createImageOutputStream(outputFile);
            imageWriter.setOutput(imageOutputStream);
            imageWriter.write(null, new IIOImage(bi, null, null), params);
            imageOutputStream.close();
            imageWriter.dispose();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    static public int getClosestPOT(int number, boolean higher) {
        int ret = 2;
        while (ret < number) {
            ret *= 2;
            if (!higher && ret > number) {
                ret /= 2;
                return ret;
            }
        }
        return ret;
    }

    static public BufferedImage getScaled(BufferedImage img,
            int targetWidth,
            int targetHeight) {

        BufferedImage scaledImage = new BufferedImage(
                targetWidth, targetHeight, img.getType());// BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = scaledImage.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.drawImage(img, 0, 0, targetWidth - 1, targetHeight - 1, null);
        return scaledImage;
    }
   
   
}

/* ################################################### */



TAGS:
Power of two image converter resizer POT

Monday, July 1, 2013

Make sprite sheet from frame files with auto-crop and merge.

My another tool for GrehGameEngine Tools Collection.

It takes all user provided images then auto-crops them and merges to build a sprite sheet. It also exports frame information file containing frames information in % of sheet size.

USAGE:
java -jar SpriteSheetFromPNGs.jar "1st.png" "2nd.png" "3rd.png" "#.png" ...  more files, file names are not fixed you can use any png files.

OUTPUT:
"sheet.png" with all images cropped and merged. "sheet.png.conf" with frames position and size data relative to "sheet.png". i.e. x, y, w, h of frames in % of sheet's size.


Download:
SpriteSheetFromPNGs.7z


Requirements:
It is a java app and needs Java 5.


Tags: sprite, sprite sheet,  auto crop and merge

Monday, June 17, 2013

Making vector like smooth gradients in GIMP

I always wanted to know how graphics like angry birds, CutTheRope etc are made then i came to know about Inkscape, vector arts. But i want to master few software instead of learning 100s. So i wanted to achieve the same in GIMP.

Except shape editing i find GIMP robust for most of my needs, so i surrendered using Inkscape. I use Inkscape for making shapes mostly. Now GIMP does all my job :).

I have written a simple short tutorial to do smooth gradients often found in vector graphics. This tutorial will give you some simple but powerful tips how to choose colors and opacity to play with smooth color gradients.


Below is the download link of the tutorial in PDF format for offline use:
GIMP_smooth_vector_like_gradients.pdf.7z

Or online version below:


Requirements:
  1. GIMP 2.8 is recommended, otherwise 2.6 if 2.8 is not available.
  2. Some basic knowledge of PC and graphics terms like alpha, hue, saturation etc
  3. Basic usage of GIMP or other app with layers.

The only tools and settings we will use most:
  1. Airbrush tool, opacity & size of brush.
  2. HSV instead of RGB colors,


Steps:
  1. Create new project 640x480 or above resolution with transparent background.
  2. Fill background with dark green color. [ HSV = 124, 99, 63 ]
  3. Select Airbrush tool with size = 20, opacity = 100, same above green foreground color.
  4. Select brush blurred circle, size = 51x51, Hardness 075.
  5. Create new layer and click it to make active work layer.


Things to note:
We have the same green color which will not produce any effect on the background color then why we picked it?

Because we want to use HSV which allows us great color match & closer difference in surrounding colors using Saturation & Value (lightness).

Notations:
H = Hue, S = Saturation, V = Value (lightness), O= Opacity, Sz = Size,

Back to steps:
  1. Now click the FG color box to change the color of our brush.

  2. Look at the color formats available: H, S, V, R, G, B. We will always use HSV not RGB because altering RGB values changes colors undesired way.
  3. Now we can move V slider to change darkness/lightness of a color and S slider to change the color strength. H values for choosing different colors.
  4. We will select V = 40.
  5. Now draw an irregular line (L1) in the blank layer. Now set V = 30.
  6. Use “Fuzzy select tool” and select that irregular line. Click “Menu -> Select -> To Path”.
  7. Go to path tab near layers tab (dockable layers dialog), enable it with eye button at left. Switch to Layers tab and select path tool.
  8. Select the path and move it to above-right of the last selected irregular line. Use Move(alt) option in path tool to move path. Click selection from path. Hide path again.
  9. Now fill this selection with the airbrush. V = 40, O = 50, Sz = 30. The Mouse pointer should be over the top-right border of the selection so that middle of the selection fills lighter green color. Unselect all to remove selection. Now the lines should look like a walking path (L2).













  1. Now change Brush, V = 75, Sz = 50, O = 100. Draw below L1. Will give output like this -




 
  1. Using this method we can make a smooth color gradient just like we get in vector art apps like inkscape. But GIMP gives us much more powerful control.
  2. Below is final output of what i made while writing this tutorial using some objects i made separately. Viz -




















 
The water like thing at bottom of image was just made using MODE of layer color combination not any pen or brush.

You can try it: Make new layer above your green layer, make figures or anything, from mode select saturation, value, dodge etc whichever looks cool to you.

All these gfx were made using the method i described above. You have to play with V, S, O, Sz and brushes, Use Opacity to get lighter color instead of changing V every time.

There are lots of techniques to play with but one good trick of making quick graphics is to make bigger gfx using airbrush tool and downscale it then use pixel editing if desired.


Lets make a simple grass:
  1. Create a new project 640x480 with transparent background.
  2. Now create a grass like shape with closed boundaries so that we can fill it with green color.
  3. Now select the grass shape with Fuzzy select tool and click Menu => Select => Save to channel (keep important selections saved into channels). Get back to the layers tab and create a new layer. Keep selection active!

NOTE:
Always try to keep outlines in separate layer to keep it unaltered and pick selections from them and work on new layers. It means select something in a layer using selection tools and create new layers and do editing on them. This is important so that any editing we do shouldn't spread outside of the boundaries ( the outline ) of our target object and our outlines remain available as it is.

  1. Now fill this new layer with green color with your choice of SV values .
  2. Now select Airbrush tool, choose a darker value and apply shade on left of the grass.
  3. It is important that you shade color in gradient way, means left to right => dark to light. To achieve this try not to change V, just reduce opacity to draw less darker towards right.
  4. The above shade means grass has some light source at right side. Without shading grass will be plain single color filled paint. Can you image what shade objects have at night/dark? Of course nothing but black, That is why keep smarter shading assuming all light sources including the color mixing due to nearby objects. Suppose a blue orb a left, then apply little blue reflective shading at left to produce more realistic output.

NOTE:
Lots of paintings fail due to bad shading, we should never apply anything without planning. While working always think this in such cases, why i am doing this? Even a single pixel can produce bad shade if added randomly.


  1. Now select higher V, means lighter green and apply to right side of grass. Just as done above. Below are the snap of all steps done yet -





















  1. Now select white, but!, in HSV white means V=100, S=0. Apply some white shade randomly where we assume direct light reflection. Adjust opacity to keep white level as per requirements.
  2. Remove the black outline and your grass is ready.

This result may or may not be up to your expectations. But the shading methods mentioned above will result in your desired one depending upon efforts made.























Impact of nearby objects:
Colors mix together, here Blue + Green = Cyan, therefore to let shading take practical color create new layer and apply shading with the object (blue here). The layout is blue shade layer above and the target object is below (grass here). Click blue shade layer and select MODE to “Addition”. This will add blue with the bottom layer i.e. green to automaticaly produce cyan.








TIP:Always reuse your work if possible. Using color, size variations of images.
- GIMP rocks :) -




GIMP TIP:
To master GIMP one has to become a player in Selection, selection modifications, path, and color selection. Above tutorial has a little demo of this all. Lots of things are there depending upon your needs. But instead of depending too much on filters try to make things yourself otherwise you probably will loose mastering art!