How to perform resize and crop with ImageMagick using the data from cropperjs?
The user can upload a large image and zoom/pan to crop. Tried using blob but it looses too much quality and times out too often.
Example from fiddle with the following data:
Original Width: 1280
Original Height: 720
Width: 424.8717011756327
Height: 238.9903319112934
X: -155.17118867901692
Y: -1.4989251522088705
Scale: 23.82
Tried with this but it crops the wrong area. Also tried scaling the original image but that's too big for the server to handle.
convert orignial.jpg -resize "1280x720^" -gravity center -crop 424x238+-155+-1 +repage result.jpg
Example:
https://jsfiddle.net/1knw3a5e/
JS code:
$(function() {
var image = $('#crop-image');
var zoomSlider = document.getElementById('zoom-slider');
var canvasSize = null;
var pictureContainer = $('.picture-frame');
var maxPictureContainerWidth = parseFloat(pictureContainer.css('max-width')) || 450;
var maxPictureContainerHeight = parseFloat(pictureContainer.css('max-height')) || 350;
var isSliderInUse = false;
// Wall is in Cm, convert to Inches to work out pixel sizes at 300dpi
var wallWpx = (0.393700787 * pictureContainer.attr('data-width')) * 300; // data-width is the wall width in pixels
var wallHpx = (0.393700787 * pictureContainer.attr('data-height')) * 300; // data-height is the wall height in pixels
var sampleImageScaleFactor = (image.attr('width') / image.attr('original-width'));
var wallSize = {
width: wallWpx * sampleImageScaleFactor, // scaling the wall size corresponding the sample size
height: wallHpx * sampleImageScaleFactor,
originalWidth: pictureContainer.attr('data-width'),
originalHeight: pictureContainer.attr('data-height')
};
var wallAspectRatio = wallSize.originalWidth/wallSize.originalHeight;
var pictureContainerSizes = {
'width': maxPictureContainerWidth * (wallAspectRatio > 1 ? 1 : wallAspectRatio) ,
'height': maxPictureContainerHeight / (wallAspectRatio > 1 ? wallAspectRatio : 1)
};
pictureContainer.css(pictureContainerSizes).removeClass('hidden');
var zoomStep = 0.2;
var biggerSide = null;
var zoomModal = $('#modal-warning');
var handleZoomHold, handleZoomFired;
image.cropper({
zoom: 0.2,
guides: false,
cropBoxResizable: false,
cropBoxMovable: false,
//viewMode: 3,
dragMode: 'move',
left: 0,
top: 0,
//width: canvasSize.width,
//height: canvasSize.height,
//aspectRatio: 1,
toggleDragModeOnDblclick: false,
zoomOnTouch: true,
zoomOnWheel: true
});
// Event
image.on('built.cropper', function() {
image.cropper('setCropBoxData', {
left: 0,
top: 0,
width: pictureContainerSizes.width,
height: pictureContainerSizes.height
});
canvasSize = {
width: image.cropper('getCropBoxData').width,
height: image.cropper('getCropBoxData').height
};
biggerSide = canvasSize.width === image.cropper('getImageData').width ? 'width' : 'height';
var savedCropperSettings = {
sliceW: parseFloat($('input[name=sliceW]').val()),
sliceH: parseFloat($('input[name=sliceH]').val()),
sliceX: parseFloat($('input[name=sliceX]').val()),
sliceY: parseFloat($('input[name=sliceY]').val()),
scale: parseFloat($('input[name=scale]').val()) // saved adoptedZoomFactor
};
if (!savedCropperSettings.scale) {
return;
}
/* restoring saved settings */
image.cropper('zoomTo', canvasSize[biggerSide]/(wallSize[biggerSide]/savedCropperSettings.scale.toFixed(1)));
var cropboxData = image.cropper('getCropBoxData');
var scaleFactor = wallSize.originalHeight / cropboxData.height;
image.cropper('setCanvasData', {
left: savedCropperSettings.sliceX / scaleFactor + cropboxData.left,
top: savedCropperSettings.sliceY / scaleFactor + cropboxData.top
});
});
var adoptedZoomFactor = NaN;
var adoptedZoomElement = $('#adoptedZoom');
image.on('crop.cropper', function() {
var data = image.cropper('getData');
var canvasData = image.cropper('getCanvasData');
var cropboxData = image.cropper('getCropBoxData');
var scaleFactor = wallSize.originalHeight / cropboxData.height;
adoptedZoomFactor = parseFloat((wallSize[biggerSide] / data[biggerSide]).toFixed(2));
adoptedZoomElement.text(adoptedZoomFactor);
$('input[name=sliceW]').val(canvasData.width * scaleFactor);
$('input[name=sliceH]').val(canvasData.height * scaleFactor);
$('input[name=sliceX]').val((canvasData.left - cropboxData.left) * scaleFactor);
$('input[name=sliceY]').val(canvasData.top * scaleFactor);
$('input[name=scale]').val(adoptedZoomFactor);
});
});
That cropper tool does not work correctly on my Mac in Safari or Firefox or Chrome. It does not respect the scale values that are entered. It always comes out with results of scale=1. Perhaps I am doing it wrong.
But if you want to do it in ImageMagick, the correct way would be:
Original:
Cropper Screen Snap:
Cropper Result (dimensions 320x180; still scale=1):
Using ImageMagick (dimensions 640x360):
ww=320
hh=180
xx=40
yy=60
rotate=0
scale=2
scale=`convert xc: -format "%[fx:$scale*100]" info:`
convert barn.jpg -virtual-pixel white -define distort:viewport=${ww}x${hh}+${xx}+${yy} -filter point -distort SRT "$rotate" +repage -resize $scale% test.jpg
Note that ImageMagick -distort SRT permits scaling, but the scale is done before the cropping from the viewport. So I had to use the viewport crop first and then add -resize in percent (as scale=2 --> scale=200%)
The reason I used -distort SRT with the viewport crop is that it would allow offset cropping when the xx and yy values are negative. You cannot do that with a simple -crop.
So for example:
ww=320
hh=180
xx=-40
yy=-60
rotate=0
scale=1
scale=`convert xc: -format "%[fx:$scale*100]" info:`
convert barn.jpg -virtual-pixel white -define distort:viewport=${ww}x${hh}+${xx}+${yy} -filter point -distort SRT "$rotate" +repage -resize $scale% test2.jpg
If you download the image, you will see it is padded at the top and right with white, but still has a size of 320x180.
If you are cropping only within the bounds of the image, then you can use -crop and the Imagemagick command would be:
ww=320
hh=180
xx=40
yy=60
rotate=0
scale=2
scale=`convert xc: -format "%[fx:$scale*100]" info:`
convert barn.jpg -crop ${ww}x${hh}+${xx}+${yy} +repage -resize $scale% test4.jpg
Which produces the same results as my original viewport crop.
I've had success just using the data from getData without doing any math on the results.
var croppable = $('.croppable');
croppable.cropper({
autoCrop: true,
viewMode: 0,
background: false,
modal: true,
zoomable: true,
responsive: false,
crop: function(e) {
data = croppable.cropper('getData');
$('#extract_image_crop_x').val(data.x);
$('#extract_image_crop_y').val(data.y);
$('#extract_image_crop_width').val(data.width);
$('#extract_image_crop_height').val(data.height);
$('#extract_image_crop_rotate').val(data.rotate);
}
});
Related
I used dom-to-image to download div content as image but it gives me a low quality image which is not clear
my code :
<script>
var node = document.getElementById('my-certificate');
var btn = document.getElementById('download-btn');
var options = {
quality: 0.99
};
btn.onclick = function() {
domtoimage.toBlob(node, options)
.then(function(blob) {
window.saveAs(blob, 'my-certification.png');
});
}
</script>
any idea in how to generate better quality ?
Use width and height in the options to scale up the node before the image will be renderd. (If you do not, the quality is like a screen-shoot)
var options = {
quality: 0.99,
width: 2000,
height: 2000,
};
Try this.
var scale = 2;
domtoimage.toBlob(domNode, {
width: domNode.clientWidth * scale,
height: domNode.clientHeight * scale,
style: {
transform: 'scale('+scale+')',
transformOrigin: 'top left'
})
This worked for me.
I'm working on a project in which i'm capturing map screenshot (using dom-to-image library) and sending it to an external API which is returning back some coordinates(x,y,w,h) after processing the sent image. These co-ordinates(rectangles) i'm trying to draw on leaflet.
As the captured image size is bigger than width and height of captured area (don't know why), I need to do scaling the co-ordinates.
Now the problem is Leaflet rectangles are not drawing on accurate position which external API is returning.
However, I'm sure that the external API is returning correct coordinates(x,y,w,h) with respect to sent image's width & height.
Something wrong i'm doing on scaling coordinates.
Below is the code snippet i'm trying:
domtoimage.toPng(document.querySelector("#map"))
.then(function (dataUrl) {
var boundsOnly = map.getBounds();
let topLeft = boundsOnly.getNorthWest();
let topRight = boundsOnly.getNorthEast();
let bottomLeft = boundsOnly.getSouthWest();
let bottomRight = boundsOnly.getSouthEast();
var currBBOXpoints = { x1y1: map.latLngToLayerPoint(topLeft),
x2y2: map.latLngToLayerPoint(topRight),
x3y3: map.latLngToLayerPoint(bottomRight),
x4y4: map.latLngToLayerPoint(bottomLeft) };
var pW = currBBOXpoints.x2y2.x - currBBOXpoints.x1y1.x;
var pH = currBBOXpoints.x3y3.y - currBBOXpoints.x1y1.y;
currBBOXpoints.pW = pW; //calculated the width of captured area
currBBOXpoints.pH = pH; //calculated the height of captured area
var i = new Image();
i.onload = function () { //calculating captured image's actual width, height
$.ajax({
type: 'post',
url: '/externalapi',
data: JSON.stringify(dataUrl),
contentType: "application/json",
dataType: "json",
success: function (resultData) {
resultData["iW"] = i.width; //captured image width
resultData["iH"] = i.height; //captured image height
resultData["currBBOXpoints"] = currBBOXpoints; //captured area bounds
drawRects(resultData);
//NOTE: Captured image's width and height are bigger than width and height of captured area (don't know why)
}
});
};
i.src = dataUrl;
});
function drawRects(rectData) {
var scale = Math.max(rectData.currBBOXpoints.pW / rectData['iW'], rectData.currBBOXpoints.pH / rectData['iH']);
var shifted_x = rectData.currBBOXpoints.pW / 2 - rectData['iW'] / 2 * scale;
var shifted_y = rectData.currBBOXpoints.pH / 2 - rectData['iH'] / 2 * scale;
rectData.od.forEach(rc => {
var modifiedX = Number(rc['x']) * scale + shifted_x;
var modifiedY = Number(rc['y']) * scale + shifted_y;
var modifiedW = (modifiedX + rc['w'])
var modifiedH = (modifiedY + rc['h'])
let point3 = map.layerPointToLatLng(L.point(modifiedX, modifiedY));
let point4 = map.layerPointToLatLng(L.point(modifiedW, modifiedH));
var rectBounds = [[point3.lat, point3.lng], [point4.lat, point4.lng]];
var boundingBox = L.rectangle(rectBounds, { color: "yellow", weight: 1, name: "rect", fillOpacity: 0.10 });
map.addLayer(boundingBox);
});
}
I can't say why but when you set the scale to 0.9 after defining x and y it matches perfect.
var scale = Math.max(currBBOXpoints.pW / imagesize.width, currBBOXpoints.pH / imagesize.height);
var x = currBBOXpoints.pW / 2 - imagesize.width / 2 * scale;
var y = currBBOXpoints.pH / 2 - imagesize.height / 2 * scale;
scale = 0.9
Also you have to set in the fiddle the css width and height:
#mapWrapper{
padding: 10px;
margin: 10px;
width: 1744px;
height: 854px;
}
This is an XY problem (which you successfully identified: the size of the capture is not the size of the map container), so let's fix the root problem instead.
It looks like dom-to-image aligns the top-left corner of the capture, but also takes into account the size of any overflowing elements (such as partially-visible map tiles on the southeast corner) when calculating the size of the capture:
(This can be proven by playing a bit with the browser's box model inspector, comparing the position & size of the southeasternmost tile with the size of the capture)
Since the capture is aligned top-left, you can just clip the capture to the size of the map canvas.
You can use the width and height options of dom-to-image together with the getSize method of L.Map to force clipping the captured image to the dimensions of the map canvas, e.g.:
domtoimage.toPng(document.querySelector("#leaflet"), {
width: map.getSize().x,
height: map.getSize().y
})
See a working demo.
I am trying to convert a html div to pdf using jsPDF. With in my div I have a svg file with background image where user can draw rectangle, line, text etc. I am using d3.js for drawing. Now I want to save my div with all drawing to pdf but it only converting my text to pdf. My js code is
function htmlToPdf() {
console.log("--------------- with in demoFromHTML");
var pdf = new jsPDF('p', 'pt', 'letter');
// source can be HTML-formatted string, or a reference
// to an actual DOM element from which the text will be scraped.
source = $('svg.plancontainer')[0];
// we support special element handlers. Register them with jQuery-style
// ID selector for either ID or node name. ("#iAmID", "div", "span" etc.)
// There is no support for any other type of selectors
// (class, of compound) at this time.
specialElementHandlers = {
// element with id of "bypass" - jQuery style selector
'#bypassme': function (element, renderer) {
// true = "handled elsewhere, bypass text extraction"
return true
}
};
margins = {
top: 80,
bottom: 60,
left: 40,
width: 522
};
// all coords and widths are in jsPDF instance's declared units
// 'inches' in this case
pdf.fromHTML(
source, // HTML string or DOM elem ref.
margins.left, // x coord
margins.top, { // y coord
'width': margins.width, // max width of content on PDF
'elementHandlers': specialElementHandlers
},
function (dispose) {
// dispose: object with X, Y of the last line add to the PDF
// this allow the insertion of new lines after html
// pdf.autoPrint();
pdf.output('dataurlnewwindow');
}, margins
);
}
and cdn is <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.3.2/jspdf.debug.js"></script>
It print PRINT AREA instead of my image and text with out svg drawing.
It is my sample div's preview that I want to convert to pdf
I did not get any specific informatin that specify where it is possible using jsPDF or not.
Now my questions are
Is it possible using jsPDF or any other js library ?
If possible, would you please suggest me?
Any kind of help are appreciated. Thanks.
I am sharing my solution that may help someone. I could not manage print svg directly using jspdf instead what I have done is first convert svg to image using https://github.com/exupero/saveSvgAsPng then use that image to create pdf. Below is my code
Get base64 image uri using svgAsPngUri method of saveSvgAsPng and pass that image through callback function
svgAsPngUri(#svgObj, options, function (uri, options) {
pdf(uri, options.pdf)
});
where I am getting image uri as uri. With in my pdf function I am using this uri to make pdf
function pdf(b64Image, options) {
console.log("--------------- passing options is ", JSON.stringify(options, null, 4));
var image = new Image();
image.src = b64Image;
console.log('--------- pdf options' + JSON.stringify(options, null, 4));
var pdf = new jsPDF(options.orientation, null, options.format);
margins = {
top: 20,
bottom: 20,
left: 20,
right: 20
};
var pdfWidth = pdf.internal.pageSize.width;
var pdfHeight = pdf.internal.pageSize.height;
var footer_height = options.f_height || 30;
var htmlPageRightOffset = 0;
var outerRacBorder = 2;
var imageDrawableHeight = pdfHeight - margins.top - margins.bottom - footer_height - outerRacBorder;
var imageDrawableWidth = pdfWidth - margins.left - margins.right - outerRacBorder;
footer = {
top: margins.top + imageDrawableHeight + outerRacBorder + 10,
bottom: 20,
left: margins.left,
right: 20,
width: 100,
height: 25,
};
company_text_position = {
x: footer.left+2,
y: footer.top + 6
};
site_text_position = {
x: company_text_position.x,
y: company_text_position.y + 6
};
floor_plan_text_position = {
x: site_text_position.x,
y: site_text_position.y + 6
};
logo_text_position = {
x: pdfWidth - margins.left - 55,
y: pdfHeight - margins.bottom - 4
};
logo_image_position = {
x: logo_text_position.x +35,
y: logo_text_position.y - 4
};
/*
Image drawing on pdf
*/
imageSize = calculateAspectRatioFit(image.width, image.height, imageDrawableWidth, imageDrawableHeight);
pdf.addImage(image, 'JPEG', margins.left + 2, margins.top + 2, imageSize.width, imageSize.height);
/*
Outer rectangle
*/
pdf.rect(margins.left, margins.top, imageDrawableWidth + outerRacBorder, imageDrawableHeight + outerRacBorder);
// pdf.rect(margins.left, imageSize.height + 10, drawableWidth, (drawableWidth - imageSize.height));
pdf.rect(footer.left, footer.top, footer.width, footer.height);
console.log(footer.left);
console.log(footer.company_x);
var footer_data = getFooterInfo();
pdf.text("Company: " + footer_data.client, company_text_position.x, company_text_position.y);
pdf.text("Site: " + footer_data.site, site_text_position.x, site_text_position.y);
pdf.text("Floor Plan: " + footer_data.floor_plan, floor_plan_text_position.x, floor_plan_text_position.y);
pdf.text("Powered by: ", logo_text_position.x, logo_text_position.y);
var logo = new Image();
logo.src = $('#logo_image').val();
console.log(logo);
logoSize = calculateAspectRatioFit(logo.width, logo.height, 20, 10);
pdf.addImage(logo, 'JPEG', logo_image_position.x, logo_image_position.y, logoSize.width, logoSize.height);
pdf.autoPrint();
pdf.save(options.name + '.pdf');
}
/**
* Conserve aspect ratio of the orignal region. Useful when shrinking/enlarging
* images to fit into a certain area.
*
* #param {Number} srcWidth Source area width
* #param {Number} srcHeight Source area height
* #param {Number} maxWidth Fittable area maximum available width
* #param {Number} maxHeight Fittable area maximum available height
* #return {Object} { width, heigth }
*
*/
function calculateAspectRatioFit(srcWidth, srcHeight, maxWidth, maxHeight) {
if(srcHeight == 0 || srcWidth == 0){
return {width: maxWidth, height: maxHeight};
}
var ratio = [maxWidth / srcWidth, maxHeight / srcHeight];
ratio = Math.min(ratio[0], ratio[1]);
return {width: srcWidth * ratio, height: srcHeight * ratio};
}
function getFooterInfo() {
var elem = $('.entityselbin .h4');
var info = {};
info.client = elem[0].innerHTML;
info.site = elem[1].innerHTML;
info.floor_plan = elem[2].innerHTML;
return info;
}
You can directly write the SVG to PDF with the latest canvg_context2d method mention in the example
https://github.com/MrRio/jsPDF/blob/master/examples/canvg_context2d/bar_graph_with_text_and_lines.html
This works for most of the svg contents.
Below is the working example of above question.
Need to include these three file refernece
$.getScript("https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.js"),
$.getScript("https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.0.272/jspdf.debug.js"),
$.getScript("https://cdnjs.cloudflare.com/ajax/libs/canvg/1.5/canvg.min.js")
function createPDF() {
var svg = '';
// Provide the SVG parent div id
if (document.getElementById("ChartId") != null) {
svg = document.getElementById("ChartId").innerHTML;
}
if (svg)
svg = svg.replace(/\r?\n|\r/g, '').trim();
var pdfData = $('#htmlContainer');//main html div
html2canvas(pdfData, {
onrendered: function(canvas) {
var contentWidth = canvas.width;
var contentHeight = canvas.height;
//The height of the canvas which one pdf page can show;
var pageHeight = contentWidth / 592.28 * 841.89;
//the height of canvas that haven't render to pdf
var leftHeight = contentHeight;
//addImage y-axial offset
var position = 0;
//a4 format [595.28,841.89]
var imgWidth = 595.28;
var imgHeight = 592.28 / contentWidth * contentHeight;
var ctx = canvas.getContext('2d');
canvg(canvas, svg, {
offsetX: 10,
offsetY: 660,
ignoreMouse: true,
ignoreAnimation: true,
ignoreClear: true,
ignoreDimensions: true
});
var pageData = new Image();
pageData = canvas.toDataURL('image/jpeg', 1.0);
var pdf = new jsPDF('l', 'pt', 'a4', true);
if (leftHeight < pageHeight) {
pdf.addImage(pageData, 'JPEG', 100, 20, imgWidth, imgHeight);
} else {
console.log('page 2');
while (leftHeight > 0) {
pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
leftHeight -= pageHeight;
position -= 841.89;
//avoid blank page
if (leftHeight > 0) {
pdf.addPage();
}
}
}
pdf.save('Test.pdf');
}
});
}
Hope this will be helpful.
I gone through documentation of cropper by fengyuanchen. I want the image to be fit by default into canvas if rotated. But I couldnt find a way to achieve this. Any idea how to achieve this functionality?
I want it to be like this to be default: link
Check issue demo here: link
I fixed this behavior but for my special needs. I just needed one rotate button which rotates an image in 90° steps. For other purposes you might extend/change my fix.
It works in "strict" mode by dynamically change the cropbox dimensions.
Here my function which is called, when I want to rotate an image. Ah and additionally the misplacement bug has also been fixed.
var $image;
function initCropper() {
$image = $('.imageUploadPreviewWrap > img').cropper({
autoCrop : true,
strict: true,
background: true,
autoCropArea: 1,
crop: function(e) {
}
});
}
function rotateImage() {
//get data
var data = $image.cropper('getCropBoxData');
var contData = $image.cropper('getContainerData');
var imageData = $image.cropper('getImageData');
//set data of cropbox to avoid unwanted behavior due to strict mode
data.width = 2;
data.height = 2;
data.top = 0;
var leftNew = (contData.width / 2) - 1;
data.left = leftNew;
$image.cropper('setCropBoxData',data);
//rotate
$image.cropper('rotate', 90);
//get canvas data
var canvData = $image.cropper('getCanvasData');
//calculate new height and width based on the container dimensions
var heightOld = canvData.height;
var heightNew = contData.height;
var koef = heightNew / heightOld;
var widthNew = canvData.width * koef;
canvData.height = heightNew;
canvData.width = widthNew;
canvData.top = 0;
if (canvData.width >= contData.width) {
canvData.left = 0;
}
else {
canvData.left = (contData.width - canvData.width) / 2;
}
$image.cropper('setCanvasData', canvData);
//and now set cropper "back" to full crop
data.left = 0;
data.top = 0;
data.width = canvData.width;
data.height = canvData.height;
$image.cropper('setCropBoxData',data);
}
This is my extended code provided by AlexanderZ to avoid cuttong wider images than container :)
var contData = $image.cropper('getContainerData');
$image.cropper('setCropBoxData',{
width: 2, height: 2, top: (contData.height/ 2) - 1, left: (contData.width / 2) - 1
});
$image.cropper('rotate', 90);
var canvData = $image.cropper('getCanvasData');
var newWidth = canvData.width * (contData.height / canvData.height);
if (newWidth >= contData.width) {
var newHeight = canvData.height * (contData.width / canvData.width);
var newCanvData = {
height: newHeight,
width: contData.width,
top: (contData.height - newHeight) / 2,
left: 0
};
} else {
var newCanvData = {
height: contData.height,
width: newWidth,
top: 0,
left: (contData.width - newWidth) / 2
};
}
$image.cropper('setCanvasData', newCanvData);
$image.cropper('setCropBoxData', newCanvData);
Not a direct answer to the question ... but i'm betting many people that use this plugin will find this helpfull..
Made this after picking up #AlexanderZ code to rotate the image.
So ... If you guys want to ROTATE or FLIP a image that has already a crop box defined and if you want that cropbox to rotate or flip with the image ... use these functions:
function flipImage(r, data) {
var old_cbox = $image.cropper('getCropBoxData');
var new_cbox = $image.cropper('getCropBoxData');
var canv = $image.cropper('getCanvasData');
if (data.method == "scaleX") {
if (old_cbox.left == canv.left) {
new_cbox.left = canv.left + canv.width - old_cbox.width;
} else {
new_cbox.left = 2 * canv.left + canv.width - old_cbox.left - old_cbox.width;
}
} else {
new_cbox.top = canv.height - old_cbox.top - old_cbox.height;
}
$image.cropper('setCropBoxData', new_cbox);
/* BUG: When rotated to a perpendicular position of the original position , the user perceived axis are now inverted.
Try it yourself: GO to the demo page, rotate 90 degrees then try to flip X axis, you'll notice the image flippped vertically ... but still ... it fliped in relation to its original axis*/
if ( r == 90 || r == 270 || r == -90 || r == -270 ) {
if ( data.method == "scaleX") {
$image.cropper("scaleY", data.option);
} else {
$image.cropper("scaleX", data.option);
}
} else {
$image.cropper(data.method, data.option);
}
$image.cropper(data.method, data.option);
}
function rotateImage(rotate) {
/* var img = $image.cropper('getImageData'); */
var old_cbox = $image.cropper('getCropBoxData');
var new_cbox = $image.cropper('getCropBoxData');
var old_canv = $image.cropper('getCanvasData');
var old_cont = $image.cropper('getContainerData');
$image.cropper('rotate', rotate);
var new_canv = $image.cropper('getCanvasData');
//calculate new height and width based on the container dimensions
var heightOld = new_canv.height;
var widthOld = new_canv.width;
var heightNew = old_cont.height;
var racio = heightNew / heightOld;
var widthNew = new_canv.width * racio;
new_canv.height = Math.round(heightNew);
new_canv.width = Math.round(widthNew);
new_canv.top = 0;
if (new_canv.width >= old_cont.width) {
new_canv.left = 0;
} else {
new_canv.left = Math.round((old_cont.width - new_canv.width) / 2);
}
$image.cropper('setCanvasData', new_canv);
if (rotate == 90) {
new_cbox.height = racio * old_cbox.width;
new_cbox.width = racio * old_cbox.height;
new_cbox.top = new_canv.top + racio * (old_cbox.left - old_canv.left);
new_cbox.left = new_canv.left + racio * (old_canv.height - old_cbox.height - old_cbox.top);
}
new_cbox.width = Math.round(new_cbox.width);
new_cbox.height = Math.round(new_cbox.height);
new_cbox.top = Math.round(new_cbox.top);
new_cbox.left = Math.round(new_cbox.left);
$image.cropper('setCropBoxData', new_cbox);
}
var photoToEdit = $('.photo_container img');
$( photoToEdit ).cropper({
autoCrop : true,
crop: function(e) {}
});
$("#rotate_left_btn").click( function () {
$( photoToEdit ).cropper('rotate', -90);
var containerHeightFactor = $(".photo_container").height() / $( photoToEdit).cropper('getCanvasData').height;
if ( containerHeightFactor < 1 ) { // if canvas height is greater than the photo container height, then scale (on both x and y
// axes to maintain aspect ratio) to make canvas height fit container height
$( photoToEdit).cropper('scale', containerHeightFactor, containerHeightFactor);
} else if ( $( photoToEdit).cropper('getData').scaleX != 1 || $( photoToEdit).cropper('getData').scaleY != 1 ) { // if canvas height
// is NOT greater than container height but image is already scaled, then revert the scaling cuz the current rotation will bring
// the image back to its original orientation (landscape/portrait)
$( photoToEdit).cropper('scale', 1, 1);
}
}
I Fixed this issue hope fully. i have added or changed the option to 0 (viewMode: 0,). Now its working well.
cropper = new Cropper(image, {
dragMode: 'none',
viewMode: 0,
width: 400,
height: 500,
zoomable: true,
rotatable: true,
crop: function(e) {
}
});
document.getElementById('rotateImg').addEventListener('click', function () {
cropper.rotate(90);
});
This seems like it should be quite simple, but for some reason I can't quite wrap my brain around it. I have an image inside a "viewport" div, of which the overflow property is set to hidden.
I've implemented a simple zooming and panning with jQuery UI, however I am having trouble getting the zoom to appear to originate from the center of the viewport. I did a little screencast from Photoshop the effect I'm trying to reproduce: http://dl.dropbox.com/u/107346/share/reference-point-zoom.mov
In PS you can adjust the scaling reference point an the object will scale from that point. Obviously this is not possible with HTML/CSS/JS, so I'm trying to find the appropriate left and top CSS values to mimic the effect.
Here is the code in question, with a few unnecessary bits removed:
html
<div id="viewport">
<img id="map" src="http://dl.dropbox.com/u/107346/share/fake-map.png" alt="" />
</div>
<div id="zoom-control"></div>
javascript
$('#zoom-control').slider({
min: 300,
max: 1020,
value: 300,
step: 24,
slide: function(event, ui) {
var old_width = $('#map').width();
var new_width = ui.value;
var width_change = new_width - old_width;
$('#map').css({
width: new_width,
// this is where I'm stuck...
// dividing by 2 makes the map zoom
// from the center, but if I've panned
// the map to a different location I'd
// like that reference point to change.
// So instead of zooming relative to
// the map image center point, it would
// appear to zoom relative to the center
// of the viewport.
left: "-=" + (width_change / 2),
top: "-=" + (width_change / 2)
});
}
});
Here is the project on JSFiddle: http://jsfiddle.net/christiannaths/W4seR/
Here's the working solution. I will explain the logic at the next edit.
Function Logic:
Summary: Remember the center position of the image, relatively.
The calculations for width and height are similar, I will only explain the height calculationThe detailled explanation is just an example of function logic. The real code, with different variable names can be found at the bottom of the answer.
Calculate the center (x,y) of the #map, relative to #viewport. This can be done by using the offset(), height() and width() methods.
// Absolute difference between the top border of #map and #viewport
var differenceY = viewport.offset().top - map.offset().top;
// We want to get the center position, so add it.
var centerPosition = differenceY + viewport.height() * 0.5;
// Don't forget about the border (3px per CSS)
centerPosition += 3;
// Calculate the relative center position of #map
var relativeCenterY = centerPosition / map.height();
// RESULT: A relative offset. When initialized, the center of #map is at
// the center of #viewport, so 50% (= 0.5)
// Same method for relativeCenterX
Calculate the new top and left offsets:
// Calculate the effect of zooming (example: zoom 1->2 = 2)
var relativeChange = new_width / old_width;
// Calculate the new height
var new_height = relativeChange * old_height;
// Calculate the `top` and `left` CSS properties.
// These must be negative if the upperleft corner is outside he viewport
// Add 50% of the #viewport's height to correctly position #map
// (otherwise, the center will be at the upperleft corner)
var newTopCss = -relativeCenterY * new_height + 0.5 * viewport.height();
Change the CSS property
map.css("top", newTopCss);
Demo: http://jsfiddle.net/W4seR/12/
var map = $('#map');
var viewport = $('#viewport');
// Cache the size of the viewport (300x300)
var viewport_size = {
x: viewport.width(),
y: viewport.height()
};
map.draggable();
$('#zoom-control').slider({
min: 300,
max: 1020,
value: 300,
step: 24,
create: function() {
map.css({
'width': 300,
'left': 0,
'top': 0
});
},
slide: function(event, ui) {
var old_width = map.width();
var old_height = map.height();
var viewport_offset = viewport.offset();
var offset = map.offset();
offset = {
top: viewport_offset.top - offset.top + .5*viewport_size.y +3,
left: viewport_offset.left - offset.left + .5*viewport_size.x +3
};
// Relative offsets, relative to the center!
offset.top = offset.top / old_height;
offset.left = offset.left / old_width;
var new_width = ui.value;
var relative = new_width / old_width;
var new_height = relative * old_height;
offset = {
top: -offset.top * new_height + .5*viewport_size.y,
left: -offset.left * new_width + .5*viewport_size.x
};
var css_properties = {
width: new_width,
left: offset.left,
top: offset.top
};
map.css(css_properties);
trace((map.position().left));
}
});
I have always relied on the kindness of strangers. Pertinent changes:
// Calculate the offset as a percentage, accounting for the height of the window
var x_offset = ((map.position().left-150))/(old_width/2);
var y_offset = ((map.position().top-150))/(old_width/2);
var css_properties = {
width: new_width,
// Set the offset based on the existing percentage rather than 1/2
// then readjust for the height of the window
left: (new_width * x_offset /2 ) + 150 + "px",
top: (new_width * y_offset /2 ) + 150 + "px"
};
Replace the hardcoded 150 with a variable set on viewport instantiation if necessary.
Here is a quick working version:
http://jsfiddle.net/flabbyrabbit/chLkZ/
Probably not the neatest solution but seems to work nicely, hope it helps.
Update: sorry this only works if zoom is 0 when the map is moved.