Wednesday, June 22, 2011

Java Thumbnail Generator

I am currently working on a Java project that need a good image thumbnail generator. I have been searching trough the search engine and found a lot of Java code that uses Java Advanced Imaging (JAI) library for image manipulation (scaling, hinting, etc). Most of it looks messy :D but I found a quite good lib, well coded Groovy class (What, Groovy? - Hell, now I remember I used this before on my Grails based project).

ImageTool, a Grails plugin. I prefer to convert this to Java instead of writing my own library. Here is the original & resulting thumbnail using the library.


Original Image :


Default Thumbnail :


High Quality Thumbnail :


Square Thumbnail :

Above example show bad & good quality of the thumbnail, you should notice the difference.

Basically, Groovy run on top of JVM and Groovy also understand Java syntax. So, it is not difficult to convert this to Java. Please note that this require JAI library to be installed (or included). Here is the converted source code.

import java.awt.RenderingHints;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.awt.image.renderable.*;
import javax.media.jai.*;
import com.sun.media.jai.codec.*;

/**
 * 

A java version of thumbnail generator http://www.grails.org/plugin/image-tools.

* * @author sverdianto * @see http://www.grails.org/plugin/image-tools * @version 1.0.4 * */ class ImageTool { static Map masks = new HashMap(); static Map alphas = new HashMap(); private RenderedOp original = null; private RenderedOp image = null; private RenderedOp result = null; private RenderedOp alpha = null; private RenderedOp mask = null; /** * Removes the accelaration lib exception */ static { System.setProperty("com.sun.media.jai.disableMediaLib", "true"); } /** * Should a thumbnail be created only if it will be smaller in size than the * current image? */ boolean decreaseOnly = true; /** * Returns the height for the currently loaded image * * @return height of the currently loaded image */ public int getHeight() { return image.getHeight(); } /** * Returns the width for the currently loaded image * * @return width of the currently loaded image */ public int getWidth() { return image.getWidth(); } /** * Saves a snapshot of the currently loaded image * */ public void saveOriginal() { original = (RenderedOp) image.createSnapshot(); } /** * Restores a snapshot onto the original image. * */ public void restoreOriginal() { image = (RenderedOp) original.createSnapshot(); } /** * Loads an image from a file. * * @param file * path to the file from which the image should be loaded * @throws IOException */ public void load(String file) throws IOException { FileSeekableStream fss = new FileSeekableStream(file); image = JAI.create("stream", fss); } /** * Loads a mask from a file and saves it on the cache, indexed by the file * name * * @throws IOException */ public void loadMask(String file) throws IOException { mask = (RenderedOp) ImageTool.masks.get(file); if (mask == null) { FileSeekableStream fss = new FileSeekableStream(file); mask = JAI.create("stream", fss); masks.put(file, mask); } } /** * Loads an alpha mask from a file and saves it on the cache * * @throws IOException */ public void loadAlpha(String file) throws IOException { alpha = (RenderedOp) ImageTool.alphas.get(file); if (alpha == null) { FileSeekableStream fss = new FileSeekableStream(file); alpha = JAI.create("stream", fss); alphas.put(file, alpha); } } /** * Overwrites the current image with the latest result image obtained. */ public void swapSource() { image = result; result = null; } /** * Loads an image from a byte array. * * @param bytes * array to be used for image initialization * @throws IOException */ public void load(byte[] bytes) throws IOException { ByteArraySeekableStream byteStream = new ByteArraySeekableStream(bytes); image = JAI.create("stream", byteStream); } /** * Writes the resulting image to a file. * * @param file * full path where the image should be saved * @param type * file type for the image * @see Possible * JAI encodings */ public void writeResult(String file, String type) throws IOException { FileOutputStream os = new FileOutputStream(file); JAI.create("encode", result, os, type, null); os.close(); } /** * Returns the resulting image as a byte array. * * @param type * file type for the image * @see Possible * JAI encodings */ public byte[] getBytes(String type) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); JAI.create("encode", result, bos, type, null); return bos.toByteArray(); } /** * Creates a thumbnail of a maximum length and stores it in the result image * * @param edgeLength * Maximum length */ public void thumbnail(float edgeLength) { if (getHeight() < edgeLength && getWidth() < edgeLength && decreaseOnly) { result = image; } else { boolean tall = (getHeight() > getWidth()); float modifier = edgeLength / (float) (tall ? getHeight() : getWidth()); ParameterBlock params = new ParameterBlock(); params.addSource(image); params.add(modifier);// x scale factor params.add(modifier);// y scale factor params.add(0.0F);// x translate params.add(0.0F);// y translate params.add(new InterpolationNearest());// interpolation method result = JAI.create("scale", params); } } /** * This method creates a thumbnail of the maxWidth and maxHeight it takes as * a parameter * * Example : Calling the method thumnailSpecial(640, 480, 1, 1) will never * produce images larger than 640 on the width, and never larger than 480 on * the height and use InterpolationBilinear(8) and scale * * @param maxWidth * The maximum width the thumbnail is allowed to have * * @param maxHeigth * The maximum height the thumbnail is allowed to have * * @param interPolationType * Is for you to choose what interpolation you wish to use 1 : * InterpolationBilinear(8) // Produces good image quality with * smaller image size(byte) then the other two 2 : * InterpolationBicubic(8) // Supposed to produce better than * above, but also larger size(byte) 3 : InterpolationBicubic2(8) * // Supposed to produce the best of the three, but also largest * size(byte) * * @param renderingType * Too choose the rendering type 1: Uses scale // Better on * larger thumbnails 2: Uses SubsampleAverage // Produces clearer * images when it comes to really small thumbnail e.g 80x60 */ public void thumbnailSpecial(float maxWidth, float maxHeight, int interPolationType, int renderingType) { if (getHeight() <= maxHeight && getWidth() <= maxWidth) { /** * Don't change, keep it as it is, even though one might loose out * on the compression included below (not sure) */ result = image; } else { boolean tall = (getHeight() * (maxWidth / maxHeight) > getWidth()); float modifier = maxWidth / (float) (tall ? (getHeight() * (maxWidth / maxHeight)) : getWidth()); ParameterBlock params = new ParameterBlock(); params.addSource(image); /** * Had to do this because of that the different rendering options * require either float or double */ switch (renderingType) { case 1: params.add(modifier);// x scale factor params.add(modifier);// y scale factor break; case 2: params.add((double) modifier);// x scale factor params.add((double) modifier);// y scale factor break; default: params.add(modifier);// x scale factor params.add(modifier);// y scale factor break; } params.add(0.0F);// x translate params.add(0.0F);// y translate switch (interPolationType) { case 1: params.add(new InterpolationBilinear(8)); break; // Produces good image quality with smaller image // size(byte) then the other two case 2: params.add(new InterpolationBicubic(8)); break; // Supposed to produce better than above, but also larger // size(byte) case 3: params.add(new InterpolationBicubic2(8)); break; // Supposed to produce the best of the two, but also // largest size(byte) default: params.add(new InterpolationBilinear(8)); break; } switch (renderingType) { case 1: result = JAI.create("scale", params); break; case 2: RenderingHints qualityHints = new RenderingHints( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); result = JAI.create("SubsampleAverage", params, qualityHints); break; default: result = JAI.create("scale", params); break; } } } public void setImageToNull() { image = null; } /** * Crops the image and stores the result * * @param edgeX * Horizontal crop. The image will be cropped edgeX/2 on both * sides. * @param edgeY * Vertical crop. The image will be cropped edgeY/2 on top and * bottom. */ public void crop(float edgeX, float edgeY) { ParameterBlock params = new ParameterBlock(); params.addSource(image); params.add((float) (edgeX / 2));// x origin params.add((float) (edgeY / 2));// y origin params.add((float) (getWidth() - edgeX));// width params.add((float) (getHeight() - edgeY));// height result = JAI.create("crop", params); } /** * Crops the image to a square, centered, and stores it in the result image * */ public void square() { float border = getWidth() - getHeight(); float cropX, cropY; if (border > 0) { cropX = border; cropY = 0; } else { cropX = 0; cropY = -border; } crop(cropX, cropY); } /** * Applies the currently loaded mask and alpha to the image */ public void applyMask() { ParameterBlock params = new ParameterBlock(); params.addSource(mask); params.addSource(image); params.add(alpha); params.add(null); params.add(new Boolean(false)); result = JAI.create("composite", params, null); } }

Here is the example how to use it.
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        ImageTool imageTool = new ImageTool();
        imageTool.load("/home/sverdianto/Pictures/tiger.jpg");
        imageTool.thumbnail(100);
        imageTool.writeResult("/home/sverdianto/Pictures/a.png", "PNG");

        ImageTool imageTool2 = new ImageTool();
        imageTool2.load("/home/sverdianto/Pictures/tiger.jpg");
        imageTool2.thumbnailSpecial(100, 100, 3, 2); // see the thumbnailSpecial JavaDoc
        imageTool2.writeResult("/home/sverdianto/Pictures/b.png", "PNG");

        ImageTool imageTool3 = new ImageTool();
        imageTool3.load("/home/sverdianto/Pictures/tiger.jpg");
        imageTool3.thumbnailSpecial(100, 100, 3, 2);
        imageTool3.swapSource();
        imageTool3.square(); // square the thumbnail
        imageTool3.writeResult("/home/sverdianto/Pictures/c.png", "PNG");
    }
}

Tuesday, June 21, 2011

Sequence generator with PlayFramework, JPA, and PostgreSQL

I have been recently making some websites using PlayFramework but has just noticed something about auto-generated Id number behavior when using PostgreSQL as database.

I usually create a Model (which is a JPA entity) by extending play.db.jpa.Model which actually have an auto generated Id property and a lot of very useful database related operations. PostgreSQL uses sequence for auto-generated number. By extending the play.db.jpa.Model, you actually use only one sequence generator for all your models. Thats making my first insert to a table (second or so, if you have more than one table) does not always start from 1.

I then decided to use one sequence per table by doing this following changes:
1. Extends play.db.jpa.GenericModel instead
2. Add an Id property to each model class

@Entity
public class Token extends GenericModel {

  @Id
  @SequenceGenerator(name = "Token_generator", sequenceName = "Token_sequence")
  @GeneratedValue(generator = "Token_generator")
  public Long id;

  ...omitted...

3. Recreate all the table

This happens only when you are using sequence as number generator (PostgreSQL and Oracle AFAIK?), MySQL should not have this kind of behavior because MySQL use AUTO_INCREMENT instead of sequence generator.

Wednesday, June 15, 2011

Hashtag Ajax using JQuery

Hashtag Ajax being more popular nowdays. Gmail, Twitter, and other modern Ajax websites applying this method in their web. So what is Hashtag Ajax?

Hashtag Ajax is an Ajax based page navigation without sacrificing SEO, browser bookmarking and back button.

So what its look like? and how it works?

Look at this following example:

http://example.com/index.html
http://example.com/about.html
http://example.com/contact.html

Instead of having those conventional URL which require browser reload when navigating each other, Hashtag ajax would use this following URL (example):

http://example.com/#!:/index.html
http://example.com/#!:/about.html
http://example.com/#!:/contact.html

Hash symbol (#) in URL commonly used as an anchor to a section of a webpage. Without javascript all above links just load the same page index.html.

Thanks to window.location.hash and JQuery ajax (or other who uses XmlHttpRequest) we can use hash to navigate pages. A simple way can do this: getting the hash value, remove the string code (in this example is #!:) and load the resulting path into JQuery.load().

I wrote a script that scan all a links with #!:[path] pattern and bind the click function using JQuery to load target pages into target div.

// call init
$(init);

function init() {
  ajax_page_handler();
  page_load($(window.location).attr("hash")); // goto first page if #!: is available
}

function page_load($href) {
  if($href != undefined && $href.substring(0, 3) == '#!:') {
    $('#content').load($href.substring(3)); // replace body the #content with loaded html
    $('html, body').animate({scrollTop:0}, 'slow'); // bonus
  }
}

/**
 * This method load #content on every url hash change
 * 
 * @return
 */
function ajax_page_handler() {
  $(window).bind('hashchange', function () {
    $href = $(window.location).attr("hash");
    page_load($href);
  });
  // this allow you to reload by clicking the same link
  $('a[href^="#!:"]').live('click', function() {
    $curhref = $(window.location).attr("hash");
    $href = $(this).attr('href');
    if($curhref == $href) {
      page_load($href);
    }
  });
}

Now, build your websites, download JQuery, and load JQury and this script inside your pages.
And then change all url to use #!:, dont forget to have main div #content as a place where content loaded and Bang!. You have a simple Ajax website.

Download the example here (http://dl.dropbox.com/u/3579854/hashtag.zip)

Monday, June 6, 2011

Ubuntu 11.04 Natty Narwhal

Setelah sekian lama menggunakan Mac, akhirnya untuk kepentingan pekerjaan saya pun beralih ke Linux (sebenernya sih gara-gara Mac nya rusyak... :P).

Sempat memilih-milih distro apa yang tepat untuk saya pakai development kegiatan sehari-hari, dengan pilihan Fedora Core 14, Ubuntu 11.04, Linux Mint.

Setelah coba install semua, kok rasanya Ubuntu 11.04 ini sedikit beda ya... Desktop managernya unik banget - namanya Unity. Secara kenyamanan saya jatuh cinta sama Unity ini yang ternyata adalah subproject dari ubuntu.

Berikut adalah gambar tampilan Ubuntu 11.04. Kelihatan keren kan? :-)


Itu dari segi tampilan :D gimana dengan fitur-fitur lainnya?
Well, dibanding dengan Fedora Core, aplikasi yang terinstall di Ubuntu lebih stabil. Mulai dari Video Player, Control Panel, Development Tools dan Package Manager boleh diacungkan Jempol. Yang paling saya suka dari Ubuntu ini adalah ketika Modem HSDPA saya sudah otomatis terdeteksi dan jalan hanya dengan konfigurasi melalui kontrol panel GUI yang sangat minimal - No More Command Line dan Driver Compile :)).

Coba deh... saya menunggu versi 11.10-nya nih... semoga banyak improvement Ubuntu.

Monday, November 22, 2010

Reposting old blog entries...

Baru ngerepost postingan lama dari sverdianto.net, banyak yang ilang :(
Tapi gpp deh menuh2in blog :P

Mewarnai OS X Terminal.app :)

Udah cari-cari dari lama gimana caranya bikin Terminal di OS X berwarna kayak di Linux, akhirnya nemu juga

Caranya gamphang :) :

  • Pastikan terminal typenya xterm-color, bisa di cek di Terminal -> Preferences
  • Edit atau buat file ~/bash_profile.
  • export CLICOLOR=1 export LSCOLORS=ExFxCxDxBxegedabagacad
  • Exit terminal dan…

Ini gambarnya:
[[IMAGE LOST]]

Selamat mencoba…

update:
Ternyata warnanyapun bisa di customize ini detailnya:

Variabel LSCOLORS itu ternyata menyatakan warna yang ditampilkan oleh program ls.
Warna yang bisa digunakan:

a - black
b - red
c - green
d - brown
e - blue
f - magenta
g - cyan
h - light grey
x - default foreground or background
Jika ingin dicetak tebal, lambang warna tersebut bisa dituliskan dengan huruf besar (uppercase).

Warna-warna tersebut dituliskan berdasarkan susunan attribute (setiap attribute terdiri dari foreground diikuti dengan background):

directory
symbolic link
socket
pipe
executable
block special
character special
executable with setuid bit set
executable with setgid bit set
directory writable to others, with sticky bit
directory writable to others, without sticky bit


ref:
- MacTips
- http://www.wikihow.com/Change-Colors-and-Set-Paths-&-Aliases-in-Your-Startup-File

Friday, January 8, 2010

How to prevent non root to execute a perl script

Here is a short script to do that. This help me much...

# check if root
$user = `whoami`;
chomp $user;
if($user ne "root") {
 print "You must be root to execute this command. Please login as root or use sudo command!\n";
 exit;
}