Skip to content

Instantly share code, notes, and snippets.

@grobertson
Created March 25, 2016 22:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grobertson/6e3b60f75e49cdc6fed1 to your computer and use it in GitHub Desktop.
Save grobertson/6e3b60f75e49cdc6fed1 to your computer and use it in GitHub Desktop.
{% load static from staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Crop Imported Image">
<title>Cropper</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropper/2.2.5/cropper.min.css">
<style>
.validation_error {
border: 2px inset #f88;
}
</style>
<script>
{% autoescape off %}
sourceData = {{ image.source_image_attribution }}
{% endautoescape %}
</script>
</head>
<body>
<div class="container">
<h1 class="page-header">Preview</h1>
<div class="row">
<div class="col-sm-8">
<div style="max-height: 600px;">
<canvas id="canvas"></canvas>
<img id="image" class="img-responsive" src="/grabit/proxyimage?id={{ image.id }}" image_id="{{ image.id }}" aspect_ratio="2:1" alt="Image to crop.">
</div>
</div>
<div class="col-sm-4">
<div class="row">
<h3 class="page-header">Settings</h3>
</div>
<div class="row">
<div class="col-sm-4">
<div>
<p>Short Title</p>
<p>Alt Text</p>
<p>Credit</p>
<p>Attribution Link</p>
</div>
</div>
<div class="col-sm-8">
<div>
<p><input name="title" id="image_title" value="{{ image.title }}" style="width: 100%;" placeholder="Short title..."></p>
<p><input name="description" id="image_description" value="{{ image.image_description }}" style="width: 100%;" placeholder="Clear SEO description..."></p>
<p><input name="credit" id="image_credit" value="{{ image.image_credit }}" style="width: 100%;" placeholder="Photo via ..."></p>
<p><input name="attribution" id="image_attribution_link" value="{{ image.image_attribution_link }}" style="width: 100%;" placeholder="http://imgur.com/hJydC..."></p>
<imput name="cropX1" id="cropX1" value="{{ points.top_left_x }}" type="hidden">
<imput name="cropY1" id="cropY1" value="{{ points.top_left_y }}" type="hidden">
<imput name="cropX2" id="cropX2" value="{{ points.bottom_right_x }}" type="hidden">
<imput name="cropY2" id="cropY2" value="{{ points.bottom_right_y }}" type="hidden">
<imput name="naturalX" id="naturalX" value="{{ image.original_image_width }}" type="hidden">
<imput name="naturalY" id="naturalY" value="{{ image.original_image_height }}" type="hidden">
<imput name="source_image_attribution" id="source_image_attribution" value="" type="hidden">
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h3 class="page-header">License</h3>
<div>
<p>
<select name="license" id="image_license">
<option value="CC-BY" selected>CC-BY</option>
<option value="CC-BY-ND">CC-BY-ND</option>
<option value="CC-BY-SA">CC-BY-SA</option>
<option value="CC-CC0">CC0 [Public Domain]</option>
<option value="Licensed">Licensed</option>
</select>
</p>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h3 class="page-header">Quality</h3>
<div>
<p>
<select name="quality" id="quality">
<option value="50">Low</option>
<option value="60">Medium</option>
<option value="70" selected>High</option>
<option value="80">Very High</option>
<option value="90">Fine Detail</option>
<option value="100">Line Art</option>
</select>
</p>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h3 class="page-header">Remix Attribution</h3>
<div>
<p>
<em>Sources for remixed artwork</em>
</p>
<p id="source_image_attribution_fields">
</p>
<p><a href="javascript:addRemixSource();">Click to add</a></p>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-8">
</div>
<div class="col-sm-4">
<input type="submit" name="submit" id="submit">
</div>
</div>
</div>
</div>
<!-- Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/js/bootstrap.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropper/2.2.1/cropper.min.js"></script>
<script>
var $canvas = $('#canvas');
var $imageDom = $('#image');
var image = $imageDom[0];
var X1 = $('#cropX1');
var Y1 = $('#cropY1');
var X2 = $('#cropX2');
var Y2 = $('#cropY2');
var originalX1 = $('#cropX1').attr('value'); // Attr will get the original value
var originalY1 = $('#cropY1').attr('value');
var originalX2 = $('#cropX2').attr('value');
var originalY2 = $('#cropY2').attr('value');
var imageTitle = $("#image_title");
var imageDescription = $("#image_description");
var imageCredit = $("#image_credit");
var imageAttributionLink = $("#image_attribution_link");
var imageLicense = $("#image_license");
var imageQuality = $("#quality");
var imageSourceAttribution = document.getElementById("source_image_attribution");
var imageSourceAttributionFields = document.getElementById("source_image_attribution_fields");
// imageSourceAttribution is stored as a json object containing an array of tuples: "{ sources: [['Artist 1', 'http://link.one/destination'], ['Artist 2', 'http://link.two/destination']] }"
function start() {
var width = $(this).width();
var height = $(this).height();
var canvas = $canvas[0];
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(
this,
0, 0, this.naturalWidth, this.naturalHeight,
0, 0, width, height
);
$canvas.cropper({
aspectRatio: 2 / 1,
autoCrop: false,
strict: true,
guides: true,
movable: false,
scalable: false,
zoomable: false,
viewMode: 1,
crop: function(e) {
X1.val(upConvert(e.x));
Y1.val(upConvert(e.y));
X2.val(upConvert(e.x + e.width));
Y2.val(upConvert(e.y + e.height));
console.log("X1: " + X1.val() + " Y1: " + Y1.val() + " X2: " + X2.val() + " Y2: " + Y2.val() + " crop width: " + e.width + " crop height: " + e.height);
},
built: function(){
$imageDom.hide();
var saved_crop = restoreCrop();
if(saved_crop){
$(this).cropper('crop'); // FGR: Must be called before setData. Ask me how I know.
$(this).cropper('setData', saved_crop);
$(this).cropper('crop'); // FGR: Call again after setting dimensions to set X1,Y1/X2,Y2
}
}
});
}
if (image.complete) {
start.call(image);
} else {
$imageDom.on('load', start);
}
unserializeRemixSources();
$('#submit').on('click', save)
function save(){
if(validateForm()){
serializeRemixSources();
var form_data = {
imgCropX1: Math.round(X1.val()),
imgCropY1: Math.round(Y1.val()),
imgCropX2: Math.round(X2.val()),
imgCropY2: Math.round(Y2.val()),
image_id: $imageDom.attr('image_id'),
aspect_ratio: $imageDom.attr('aspect_ratio'),
title: $('#image_title').val(),
image_description: $('#image_description').val(),
image_credit: $('#image_credit').val(),
image_attribution_link: $('#image_attribution_link').val(),
image_license: $('#image_license').val(),
quality: $('#quality').val(),
source_image_attribution: serializeRemixSources()
};
var url = '/grabit/saveimage';
// Post the form (resulting in a save). Callback loads the admin object detail.
$.post(url, form_data, function(data){
var next_url = '/admin/grabit/croppedimage/' + form_data.image_id + '/';
window.location = next_url;
});
}else{
return false;
}
}
function restoreCrop(){
try{
var data = {
x: downConvert(originalX1),
y: downConvert(originalY1),
width: downConvert(originalX2),
height: downConvert(originalY2),
rotate: 0
}
return data;
}catch(e){
return false;
}
}
function getRatio(){
// Returns the result of the natural width of the image divided by the visible width of the canvas.
return roundToPrecision(image.naturalWidth / canvas.width, 5);
}
function upConvert(val){
// returns the value multiplied by the effective ratio
return val * getRatio() || 1;
}
function downConvert(val){
// returns the value divided by the effective ratio
return val / getRatio() || 1;
}
function roundToPrecision(value, exp) {
// Round to a specific number of decimal places, not to integer
// http://stackoverflow.com/a/21323330
if (typeof exp === 'undefined' || +exp === 0)
return Math.round(value);
value = +value;
exp = +exp;
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0))
return NaN;
// Shift
value = value.toString().split('e');
value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)));
// Shift back
value = value.toString().split('e');
return +(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp));
}
function validateForm(){
return validateField(imageTitle) && validateField(imageDescription) && validateField(imageCredit) &&validateField(imageAttributionLink) && validateURL(imageAttributionLink);
}
function validateField(el){
if(Validator.isEmptyString(el.val(), false)){
el.addClass("validation_error");
return false;
}else{
el.removeClass("validation_error");
return true;
}
}
function validateURL(el){
if(Validator.isURL(el.val())){
el.removeClass("validation_error");
return true;
}else{
el.addClass("validation_error");
return false;
}
}
function addRemixSource(name, url){
var eSourceName = document.createElement("input");
eSourceName.name = "remixSourceName[]";
eSourceName.placeholder = "Artist...";
eSourceName.style = "width: 100%;";
eSourceName.value = name || '';
var eSourceLink = document.createElement("input");
eSourceLink.name = "remixSourceLink[]";
eSourceLink.placeholder = "http://bluthcorp.com/...";
eSourceLink.style = "width: 100%;";
eSourceLink.value = url || '';
var para = document.createElement("p");
para.appendChild(eSourceName);
para.appendChild(eSourceLink);
imageSourceAttributionFields.appendChild(para);
}
function serializeRemixSources(){
var names = document.getElementsByName("remixSourceName[]");
var links = document.getElementsByName("remixSourceLink[]");
var serialized = [];
for (var i = 0, len = names.length; i < len; i++) {
if (names[i].value != '' && links[i].value != ''){
serialized.push([names[i].value, links[i].value]);
}
}
sourceData.sources = serialized;
return JSON.stringify(sourceData);
}
function unserializeRemixSources(){
var sources = sourceData.sources;
for (var i = 0, len = sources.length; i < len; i++) {
addRemixSource(sources[i][0], sources[i][1]);
}
}
var Validator = (function(){
return {
/* string validation */
isEmptyString : function(string, countWhitespace){
if(!countWhitespace){
string = string.replace(/\s+/g, '');
}
return string.length == 0;
},
isURL : function(string){
return /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/i.test(string);
}
};
})();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment