Skip to content

Instantly share code, notes, and snippets.

@cyrusbeer
Created January 26, 2012 18:25
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save cyrusbeer/1684198 to your computer and use it in GitHub Desktop.
Save cyrusbeer/1684198 to your computer and use it in GitHub Desktop.
Upload, Crop, and Resize an Image with the Play! Framework and jQuery
#{form @Staff.create(), id:'staffForm', enctype:'multipart/form-data' }
<input type="hidden" name="x1" value="" />
<input type="hidden" name="y1" value="" />
<input type="hidden" name="x2" value="" />
<input type="hidden" name="y2" value="" />
<input type="hidden" name="origWidth" value="" />
<input type="hidden" name="origHeight" value="" />
<input type="hidden" name="finalWidthAndHeight" value="200" />
<!-- this is styled with twitter bootstrap -->
<fieldset>
<legend>Add a staff member</legend>
<br/>
first name
...
<div class="clearfix">
<label for="avatarFile">Avatar</label>
<div class="input">
<input id="avatarFile" type="file" name="avatarFile" class="input-file" />
<span class="help-inline"></span>
</div>
</div>
<div id="avatar" class="clearfix" style="display:none">
<label for="avatarPreviewImage">Preview</label>
<div class="avatar-preview">
<a id="avatarHref" href="" rel="lightbox">
<img id="avatarPreviewImage" src="" style="border: none;" />
</a>
</div>
</div>
<div class="actions">
<input class="btn primary" type="submit" value="Save">
<a class="btn" href="@{Staff.index(practice.id)}">Cancel</a>
</div>
</fieldset>
#{/form}
package models;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.persistence.*;
import play.data.Upload;
import play.db.jpa.Blob;
import play.db.jpa.Model;
@Entity(name="user_file")
public class Avatar extends Model {
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="user_id")
public User user;
public String name;
@Column(name="content_type")
public String contentType;
@Column(name="data")
@Lob
public byte[] imageBytes;
public Long size;
public void setCropAndScaleAvatarUpload(Upload avatarUpload, int x1, int x2, int y1, int y2, int finalWidthAndHeight) throws IOException {
int croppedWidth = x2-x1;
int croppedHeight = y2-y1;
this.contentType = avatarUpload.getContentType();
this.name = "Avatar";
this.size = avatarUpload.getSize();
BufferedImage img = ImageIO.read(avatarUpload.asStream());
BufferedImage cropped = img.getSubimage(x1, y1, croppedWidth, croppedHeight);
BufferedImage resized = cropped;
if (croppedWidth != finalWidthAndHeight) {
resized = Avatar.getScaledInstance(cropped, finalWidthAndHeight, finalWidthAndHeight, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write( resized, "jpg", baos );
baos.flush();
this.imageBytes = baos.toByteArray();
baos.close();
}
/**
* Copied from http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html
*
* Convenience method that returns a scaled instance of the
* provided {@code BufferedImage}.
*
* @param img the original image to be scaled
* @param targetWidth the desired width of the scaled instance,
* in pixels
* @param targetHeight the desired height of the scaled instance,
* in pixels
* @param hint one of the rendering hints that corresponds to
* {@code RenderingHints.KEY_INTERPOLATION} (e.g.
* {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
* {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
* {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
* @param higherQuality if true, this method will use a multi-step
* scaling technique that provides higher quality than the usual
* one-step technique (only useful in downscaling cases, where
* {@code targetWidth} or {@code targetHeight} is
* smaller than the original dimensions, and generally only when
* the {@code BILINEAR} hint is specified)
* @return a scaled version of the original {@code BufferedImage}
*/
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 && img.getWidth() > targetWidth && img.getHeight() > targetHeight) {
// 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;
}
}
@Entity(name="file_upload")
public class FileUpload extends Model {
public String uuid;
@Column(name="file_blob")
public Blob fileBlob;
@Column(name="uploaded_on")
public Date uploadedOn;
}
// I changed the lightbox-secNav-btnClose button to be a "Crop" button styled with twitter bootstrap instead of a "Close" image.
// It is styled with twitter bootstrap
<a href="#" class="btn small" id="lightbox-secNav-btnClose">CROP</a>
public class Staff extends Application {
public static void getTemporaryAvatar() {
String uuid = session.get("fileUuid");
FileUpload fileUpload = FileUpload.find("uuid", uuid).first();
response.setContentTypeIfNotSet(fileUpload.fileBlob.type());
renderBinary(fileUpload.fileBlob.get());
}
public static void setTemporaryAvatar(File avatarFile) throws IOException, FileNotFoundException {
checkAuthenticity();
String uuid = UUID.randomUUID().toString();
FileUpload fileUpload = new FileUpload();
fileUpload.fileBlob = new Blob();
fileUpload.fileBlob.set(new FileInputStream(avatarFile), MimeTypes.getContentType(avatarFile.getName()));
fileUpload.uuid = uuid;
fileUpload.uploadedOn = new Date();
fileUpload.save();
BufferedImage img = ImageIO.read(avatarFile);
session.put("fileUuid", uuid);
// returning json throws a security error in IE, so we return html, which IE blanks out,
// but does not throw an error, and then, for ie, we make a separate request to get the
// width and height.
// see comment below
String html = "<input type='hidden' name='origWidth' value='" + img.getWidth() + "' />" +
"<input type='hidden' name='origHeight' value='" + img.getHeight() + "' />";
renderHtml(html);
}
// only used by IE. see comment above
public static void getTemporaryAvatarWidthAndHeight() throws IOException, FileNotFoundException {
String uuid = session.get("fileUuid");
FileUpload fileUpload = FileUpload.find("uuid", uuid).first();
BufferedImage img = ImageIO.read(fileUpload.fileBlob.getFile());
Map<String, String>map = new HashMap<String,String>();
map.put("width", new Integer(img.getWidth()).toString());
map.put("height", new Integer(img.getHeight()).toString());
renderJSON(map);
}
public static void create(User user, Upload avatarFile) throws IOException {
checkAuthenticity();
if (avatarFile != null) {
int x1, x2, y1, y2, finalWidthAndHeight;
x1 = Integer.parseInt(params.get("x1"));
y1 = Integer.parseInt(params.get("y1"));
x2 = Integer.parseInt(params.get("x2"));
y2 = Integer.parseInt(params.get("y2"));
finalWidthAndHeight = Integer.parseInt(params.get("finalWidthAndHeight"));
Avatar avatar = new Avatar();
avatar.setCropAndScaleAvatarUpload(avatarFile, x1, x2, y1, y2, finalWidthAndHeight);
avatar.user = user;
user.addAvatar(avatar);
}
staffMember.save();
index();
}
public static void getAvatar(Long userId) {
User user = User.findById(userId);
Avatar avatar = user.avatar;
response.setContentTypeIfNotSet(avatar.contentType);
renderBinary(new ByteArrayInputStream(avatar.imageBytes));
}
}
$(function( $ ) {
$(document).ready(function() {
resetCrop();
$("input#avatarFile").change(function() {
var val = $(this).val();
// used because this app is eventually packaged in a war file.
var warContext = $(document).data("warContext");
if (val.substring(val.length - 4) != ".jpg") {
$(this).siblings("span.help-inline").text("Must be a .jpg file").css("color", "red");
} else {
$(this).siblings("span.help-inline").text("");
$("form#staffForm").ajaxSubmit({
url: warContext + "/staff/avatar/settemp",
type: "POST",
cache: false,
target: "div#origWidthAndHeight",
error: function(jqXHR, textStatus, errorThrown) {
console.log("Error thrown:" + jqXHR + "---" + textStatus + "---" + errorThrown);
},
success: function() {
var src = warContext + "/staff/avatar/gettemp?timestamp=" + new Date().getTime();
$("#avatarPreviewImage").attr("src", src);
$("#avatarHref").attr("href", src);
var origWidth = parseInt($("input:hidden[name='origWidth']").val());
var origHeight = parseInt($("input:hidden[name='origHeight']").val());
if ($.browser.msie) {
$.ajax({
url: warContext + "/staff/avatar/gettempwh?timestamp=" + new Date().getTime(),
dataType: "json",
async:false,
type:"GET",
success: function(json) {
origWidth = parseInt(json.width);
origHeight = parseInt(json.height);
}
});
}
var widthAndHeight = parseInt($("input:hidden[name='finalWidthAndHeight']").val());
if ((origWidth < widthAndHeight) || (origHeight < widthAndHeight)) {
widthAndHeight = (origWidth < origHeight ? origWidth : origHeight);
}
//console.log("origWidth=" + origWidth + " origHeight=" + origHeight + " widthAndHeight=" + widthAndHeight);
resetCrop(widthAndHeight);
$("div.avatar-preview").find("#avatarHref").trigger("click", function() {});
}
});
}
return false;
});
$('a[rel=lightbox]').lightBox({
imageLoading: $(document).data("warContext") + '/public/images/lightbox-ico-loading.gif',
finish: function() {
$("div#avatar").show();
var x1 = $('input:hidden[name="x1"]').val();
var x2 = $('input:hidden[name="x2"]').val();
var y1 = $('input:hidden[name="y1"]').val();
var y2 = $('input:hidden[name="y2"]').val();
var finalWidthAndHeight = $('input:hidden[name="finalWidthAndHeight"]').val();
var cropWidth = parseInt(x2) - parseInt(x1);
var cropHeight = parseInt(y2) - parseInt(y1);
var origWidth = $("input:hidden[name='origWidth']").val();
var origHeight = $("input:hidden[name='origHeight']").val();
var scaleX = parseInt(finalWidthAndHeight) / (cropWidth || 1);
var scaleY = parseInt(finalWidthAndHeight) / (cropHeight || 1);
$("#avatarPreviewImage").css({
width: Math.round(scaleX * parseInt(origWidth)) + 'px',
height: Math.round(scaleY * parseInt(origHeight)) + 'px',
marginLeft: '-' + Math.round(scaleX * x1) + 'px',
marginTop: '-' + Math.round(scaleY * y1) + 'px'
});
}
});
$('a[rel=lightbox]').click(show);
});
function resetCrop() {
var x1 = '0';
var y1 = '0';
var x2 = $("input:hidden[name='finalWidthAndHeight']").val();
var y2 = x2;
$('input:hidden[name="x1"]').val(x1);
$('input:hidden[name="y1"]').val(y1);
$('input:hidden[name="x2"]').val(x2);
$('input:hidden[name="y2"]').val(y2);
}
function show() {
if ($('#lightbox-image').is(':visible')) {
var x1=$('input[name="x1"]').val();
var y1=$('input[name="y1"]').val();
var x2=$('input[name="x2"]').val();
var y2=$('input[name="y2"]').val();
$('#lightbox-image').imgAreaSelect({
x1: x1,
y1: y1,
x2: x2,
y2: y2,
aspectRatio:'1:1',
handles:true,
onSelectEnd: function (img, selection) {
$('input[name="x1"]').val(selection.x1);
$('input[name="y1"]').val(selection.y1);
$('input[name="x2"]').val(selection.x2);
$('input[name="y2"]').val(selection.y2);
},
parent: '#jquery-lightbox'
});
$('#jquery-lightbox').unbind('click');
$('#lightbox-nav').remove();
}
else {
setTimeout(show, 50);
}
}
} (jQuery) );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment