Created
January 26, 2012 18:25
-
-
Save cyrusbeer/1684198 to your computer and use it in GitHub Desktop.
Upload, Crop, and Resize an Image with the Play! Framework and jQuery
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#{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} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$(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