Canvas, get the size of picture compressed and resized - javascript

I'm using FileReader to display in a <div> a file uploaded with an input type file. When the file is dropped in the field, I need to compressed the file and resize him. I used this code :
var width = source_img_obj.naturalWidth;
var height = source_img_obj.naturalHeight;
var quality = 90;
var maxWidth = 2000; // Max width for the image
var maxHeight = 2000; // Max height for the image
var ratio = 0; // Used for aspect ratio
// Check if the current width is larger than the max
if (width > maxWidth){
ratio = maxWidth / width; // get ratio for scaling image
height = height * ratio; // Reset height to match scaled image
width = width * ratio; // Reset width to match scaled image
}
// Check if current height is larger than max
if (height > maxHeight){
ratio = maxHeight / height; // get ratio for scaling image
width = width * ratio; // Reset width to match scaled image
height = height * ratio; // Reset height to match scaled image
}
var cvs = document.createElement('canvas');
cvs.width = width;
cvs.height = height;
var ctx = cvs.getContext("2d").drawImage(source_img_obj, 0, 0, width, height);
var newImageData = cvs.toDataURL(mime_type, quality/100);
var result_image_obj = new Image();
result_image_obj.src = newImageData;
EDIT : FULL code is here
The finished file compressed is saved in result_image_obj, but before display this base64 image, I need to check the size of the final picture. So if the size is larger than 500kb I need to compress again the picture.
Is there a way to get the size (b, kb, mb..) of a base64 picture generated by canvas ?
Another question : Can we get the orientation of the picture uploaded with FileReader ? Because with some device portait picture are displayed in landscape orientation.
Thanks

You can find rough image size using:
var size = newImageData.length * 3 / 4; // size in bytes
if (size > 500 * 1024) { // more than 500 kb
// do something
}
Look here for more info:
Base64 length calculation?

Related

How to tell docx.js to use an image's natural height and width?

I'm getting an image's binary data as an ArrayBuffer, and inserting it into a document using docx.js:
getImageBinaryDataAsArrayBuffer().then(function (imageBuffer) {
var doc = new docx.Document();
var image = docx.Media.addImage(doc, imageBuffer);
doc.addSection({
children: [
new docx.Paragraph({
children: [image],
alignment: docx.AlignmentType.CENTER
}),
]
});
docx.Packer.toBlob(doc).then(function (blob) {
// save the doc somewhere
})
}).catch(function (err) {
console.log(err);
});
This works, but it seems like the image size is defaulting to 100x100, and is not preserving the aspect ratio. In the docx.js documentation for Images, it looks like you can specify the height and width when you add the image to the doc:
Media.addImage(doc, [IMAGE_BUFFER], [WIDTH], [HEIGHT], [POSITION_OPTIONS]);
but I don't know the image's natural height and width since all I'm working with is an ArrayBuffer. (Can you determine an image's height and width from ArrayBuffer data? My gut says no...)
Is there a way to tell docx.js to use the image's natural height and width? Or at least to preserve the aspect ratio?
You can get the image dimension from the binary format as well. Here is my code which preserves aspect ratio when you need to resize the image to specific width/height
import imageSize from 'image-size'; // https://www.npmjs.com/package/image-size
function fitImage(doc, image, targetWidth?, targetHeight?) {
const originalSize = imageSize(image);
const originalAspectRatio = originalSize.width / originalSize.height;
let width: number;
let height: number;
if (!targetWidth) {
// fixed height, calc width
height = targetHeight;
width = height * originalAspectRatio;
} else if (!targetHeight) {
// fixed width, calc height
width = targetWidth;
height = width / originalAspectRatio;
} else {
const targetRatio = targetWidth / targetHeight;
if (targetRatio > originalAspectRatio) {
// fill height, calc width
height = targetHeight;
width = height * originalAspectRatio;
} else {
// fill width, calc height
width = targetWidth;
height = width / originalAspectRatio;
}
}
console.log(originalSize, originalAspectRatio, width, height);
return Media.addImage(doc, image, width, height);
}
const image = fitImage(doc, imageBuffer, 100); // set width to 100px calc height
const image = fitImage(doc, imageBuffer, null, height); // set height to 100px calc width
const image = fitImage(doc, imageBuffer, 100, 100); // fit image inside a 100x100 box

responsive canvas on window resize event

I am new one to canvas concept,I am trying to draw canvas using D3.js. I want to make canvas as responsive based on window screen size.
function onResize(){
var element = document.getElementsByTagName("canvas")[0];
var context = element .node().getContext("2d");
var scrnWid = window.innerWidth,
scrnHgt = window.innerHeight,
elementHgt = element.height,
elementWid = element.width;
var aspWid = elementWid/scrnWid;
var aspHig = elementHgt/scrnHgt;
context.scale(aspWid,aspHig);
}
window.addEventListener("resize",onResize);
This is the code I used to resize canvas, but it not working.I don't want to use any library except D3.js. Can anyone suggest me better solution ?
2DContext.scale() changes rendered content not display size / resolution
You are not changing the canvas size, all you are doing is scaling the content of the canvas.
You can set the page size of the canvas via its style properties
canvas.style.width = ?; // a valid CSS unit
canvas.style.height = ?; // a valid CSS unit
This does not affect the resolution (number of pixels) of the canvas. The canvas resolution is set via its width and height properties and is always in pixels. These are abstract pixels that are not directly related to actual device display pixels nor do they directly relate to CSS pixels (px). The width and height are numbers without a CSS unit postfix
canvas.width = ?; // number of pixel columns
canvas.height = ?; // number of pixel rows
Setting the 2D context scale has no effect on the display size or the display resolution, context.scale(?,?) only affects the rendered content
To scale a canvas to fit a page
const layout = { // defines canvas layout in terms of fractions of window inner size
width : 0.5,
height : 0.5,
}
function resize(){
// set the canvas resolution to CSS pixels (innerWidth and Height are in CSS pixels)
canvas.width = (innerWidth * layout.width) | 0; // floor with |0
canvas.height = (innerHeight * layout.height) | 0;
// match the display size to the resolution set above
canvas.style.width = canvas.width + "px";
canvas.style.height = canvas.height + "px";
}

How to draw an image in real size and ensure printing will be correct

I don't really know how to formulate my problem into one question but here is a good description:
Imagine I want to draw a square on an HTML canvas, with dimension 1" x 1".
I know that using pixels as a printable length is meaningless, we need to take in account the DPI. In the following example I took a dpi of 300:
<canvas id="myCanvas" width="400" height="400"></canvas>
This is my Javascript:
var dpi = 300;
var cContext = document.getElementById("myCanvas").getContext("2d");
cContext.moveTo(50, 50);
// Draw a square
cContext.lineTo(350, 50);
cContext.lineTo(350, 350);
cContext.lineTo(50, 350);
cContext.lineTo(50, 50)
cContext.stroke();
The result is a nice square with width 300px in a 300dpi setting, so it should print a one inch square when printing on paper. But the problem is that it doesn't. I checked the printer settings and used 300dpi.
Can someone tell me what I'm doing wrong, or point me in the right direction?
Print quality/DPI
The printer's DPI setting is not related to the source image DPI and is commonly known as print quality.
You need the print size
The image DPI is dependent on its resolution (width and height in pixels) and the size it is printed at mm or inches to give printing resolution (iDPU "image Dots Per Unit" for further reference in this answer).
The image DPI is meaningless unless you also associate a fixed print size to the image.
If the printer has a DPI set differently to the iDPI of the image the printer (or driver) will either downsample or upsample the image DPI to match the required DPI. You want to avoid downsampling as that will reduce the image quality.
Page Size
To get the printer DPI to match the image iDPU . This example is for an A4 page in portrait mode. You can use any size print page but you will need to know the actual physical size.
const pageSizes = {
a4 : {
portrait : {
inch : {
width : 8.27,
height : 11.69,
},
mm : {
width : 210,
height : 297,
}
},
landscape : {
inch : {
height : 8.27,
width : 11.69,
},
mm : {
width : 297,
height : 210,
}
},
}
Canvas resolution
Create a canvas with a iDPI 300 to be 2 inches by 2 inches.
const DPI = 300; // need to have a selected dots per unit in this case inches
const units = "inch";
const pageLayout = "portrait";
const printOn = "a4";
// incase you are using mm you need to convert
const sizeWidth = 2; // canvas intended print size in units = "inch"
const sizeHeight = 2;
var canvas = document.createElement("canvas");
canvas.width = DPI * sizeWidth;
canvas.height = DPI * sizeHeight;
Canvas size
Scale the canvas to fit the page at the correct size to match the print size. This will be the canvas print size/page print width
canvas.style.width = ((sizeWidth / pageSizes[printOn][pageLayout][units].width) * 100) + "%";
For the height you need to assume that the pixel aspect is square and the height of the page may be longer or shorter than the print page so you must use pixels.
canvas.style.height = Math.round((sizeHeight / pageSizes[printOn][pageLayout][units].width) * innerWidth) + "px";
Add the canvas to the page
document.body.appendChild(canvas);
Note: the canvas must be added to the page body or an element that is 100% of the page width. IE the width in pixels === innerWidth.
Note: the canvas should not have a border, padding, margin or inherit any CSS styles that affect its size.
Printing
Ensure the printer quality is set to 300DPI (or higher).
Select the page size (in this case A4 is 210 × 297 millimeters or 8.27 × 11.69 inches)
Select the layout to be portrait.
Select no borders on the print options.
Print
Printing with borders.
If you want borders on the printed page you need to use custom borders so that you know the border size. (NOTE example is only using inches)
const border = { // in inches
top : 0.4,
bottom : 0.4,
left : 0.4,
right : 0.4,
}
Create the canvas as shown above.
Size the canvas
canvas.style.width = ((sizeWidth / (pageSizes.a4.portrait.width - border.left - border.right) * 100) + "%";
The height becomes a little more complex as it needs the pixel width of the page adjusted for the borders.
canvas.style.height = Math.round((sizeHeight / (pageSizes.a4.portrait.width - border.left - border.right)) * innerWidth * (1 - (border.left - border.right) / pageSizes.a4.portrait.width) ) + "px";
I noticed in the comments you measured in cm and not inches (good choice, metric system FTW), so here's a commented example that handles those measures correctly
var dpi = 300; // the print resolution
var ppmm = dpi / 25.4; // get the pixel-per-millimeter ratio
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var x = 5 * ppmm; // 5mm
var y = 5 * ppmm; // 5mm
var w = 76 * ppmm; // 76mm
var h = 76 * ppmm // 76mm
ctx.rect(x, y, w, h); // drawing a rectangle the simple way
ctx.stroke();
var data = canvas.toDataURL(); // extract the image data
// inject the image data into a link, creating a downloadable file
var link = document.getElementById("link");
link.setAttribute('href', 'data:application/octet-stream;charset=utf-16le;' + data);
link.setAttribute('download', "image.png");
<!-- the canvas is scaled down from 300dpi to 96dpi it shouldn't affect the final image but it makes it nicer for the screen and more manageable -->
<canvas id="canvas" width="1125px" height="1125px" style="width:360px;height:360px"></canvas>
download
to print a 300dpi image of 7.5cm you have to get the pixel-per-mm ratio and multiply your sizes to that.
When you download the image, it will be a 72dpi image of 1125x1125px, resample that to 300dpi and you'll get 270x270px which equals to 95,25mm when printed (for the whole image, padding and all).
the rectangle drawn will have a 897.6377952755906px side length, with prints out to 76mm at 300dpi
btw, that fractional pixel size is the reason why i scale down the canvas on the screen. Since we're prioritising the print, the screen image will suffer and look blurry, scaling it down will make it look crispier. Plus it's a more manageable size to see in the browser.
final note: apparently a retina screen uses a different dpi value to render the canvas, so the rectangle will look smaller. on a "normal" resolution monitor it will measure 7.6cm on screen too.

Angularjs 2 typescript app permentaly moved

When i run my application it gives me several errors:
the first one is:
SyntaxError: class is a reserved identifier in the class thumbnail
code:
const MAXHEIGHT = 170;
const MAXWIDTH = 320;
import {Subcategory} from './subcategory'
//import {Category} from './category'
import {bootstrap} from 'angular2/platform/browser'
class Thumbnail {
width: number;
height: number;
constructor(element: HTMLImageElement) {
this.height = element.height;
this.width = element.width;
}
resize(oImage: HTMLImageElement) {
var maxWidth = 100; // Max width for the image
var maxHeight = 100; // Max height for the image
var ratio = 0; // Used for aspect ratio
var width = oImage.width; // Current image width
var height = oImage.height; // Current image height
// Check if the current width is larger than the max
if (oImage.width > MAXWIDTH) {
ratio = MAXWIDTH / width; // get ratio for scaling image
oImage.width=MAXWIDTH; // Set new width
oImage.height=height * ratio; // Scale height based on ratio
height = height * ratio; // Reset height to match scaled image
width = width * ratio; // Reset width to match scaled image
}
// Check if current height is larger than max
if (height > MAXHEIGHT) {
ratio = MAXHEIGHT / height; // get ratio for scaling image
oImage.height= MAXHEIGHT; // Set new height
oImage.width= width * ratio; // Scale width based on ratio
width = width * ratio; // Reset width to match scaled image
height = height * ratio; // Reset height to match scaled image
}
// Check if current height is smaller than max
if (height < MAXHEIGHT) {
ratio = MAXHEIGHT / height; // get ratio for scaling image
oImage.height = MAXHEIGHT; // Set new height
oImage.width = width * ratio; // Scale width based on ratio
width = width * ratio; // Reset width to match scaled image
height = height * ratio; // Reset height to match scaled image
}
// Check if the current width is smaller than the max
if (oImage.width < MAXWIDTH) {
ratio = MAXWIDTH / width; // get ratio for scaling image
oImage.width = MAXWIDTH; // Set new width
oImage.height = height * ratio; // Scale height based on ratio
height = height * ratio; // Reset height to match scaled image
width = width * ratio; // Reset width to match scaled image
}
document.body.appendChild(oImage);
console.log(oImage.height);
console.log(oImage.width);
}
};
window.onload = () => {
var oImage = new Image();
var oImage2 = new Image();
// var category = new Category(1, "string");
var subcategory = new Subcategory("teste",1,2);
oImage.src = 'AngularJS.png';
oImage.setAttribute('class','img-responsive');
oImage2.src = 'ts.jpg';
var thumbnail = new Thumbnail(oImage);
var thumbnail2 = new Thumbnail(oImage2);
thumbnail.resize(oImage);
thumbnail2.resize(oImage2);
console.log(oImage.height);
console.log(oImage.width);
};
The second error is:
GET http://localhost:51580/app
301 Moved Permanently
The configuration of angular is:
<script>
System.config({
packages: {
TypeScriptHTMLApp1: {
format: 'register',
defaultExtension: 'js'
}
}
});
System.import('app')
.then(null, console.error.bind(console));
</script>
I already tried to create a folder named app and moved the file for that folder but the error persists. I need help with this, I lost several hours and can't resolve anything. I attach one image with the file structure and another one with the firebug errors. Thanks in advance.
as suggested by #Eric in comment
Classes aren't supported in Firefox version < 45 according to this article
. instead try same use case in the chrome.
posted as answer may helpful to someone.

Resizing to fit height jquery.panzoom

Im trying to work out how to make an image that is way bigger than the browser window (2000x2800px) scale when initializing jquery.panzoom. I need the image to resize itself so it will fit within the height of the container.
Check out this jsFiddle: http://jsbin.com/raxejirubayu/1/edit?html,css,js,output
Anyone with any experience using panzoom able to help me out? Virtual beers and highfives given in return.
I've fixed this issue by writing this javascript code, it basically calculates the zoom and pan parameters according to the image size and container size and applies transformations such that the image is stretched to the container boundary.
Javascript file
$(document).ready(function(){
$panzoom = $(".cimage").panzoom();
//Wait for the image to load
$('.cimage').load(function(){
//Get container and image
var image = $('.cimage');
var container = $('.image-container');
//Get container dimensions
var container_height = container.height();
var container_width = container.width();
//Get image dimensions
var image_height = image.height();
var image_width = image.width();
//Calculate the center of image since origin is at x:50% y:50%
var image_center_left = image_width / 2.0;
var image_center_top = image_height / 2.0;
//Calculate scaling factor
var zoom_factor;
//Check to determine whether to stretch along width or heigh
if(image_height > image_width)
zoom_factor = container_height / image_height;
else
zoom_factor = container_width / image_width;
//Zoom by zoom_factor
$panzoom.panzoom("zoom", zoom_factor, {animate: false});
//Calculate new image dimensions after zoom
image_width = image_width * zoom_factor;
image_height = image_height * zoom_factor;
//Calculate offset of the image after zoom
var image_offset_left = image_center_left - (image_width / 2.0);
var image_offset_top = image_center_top - (image_height / 2.0);
//Calculate desired offset for image
var new_offset_left = (container_width - image_width) / 2.0;
var new_offset_top = (container_height - image_height) / 2.0;
//Pan to set desired offset for image
var pan_left = new_offset_left - image_offset_left;
var pan_top = new_offset_top - image_offset_top;
$panzoom.panzoom("pan", pan_left, pan_top);
});
});
HTML file
<div class="image-container">
<img class='cimage' src="abc.jpg'/>
</div>
It is stretched along width or height, depending upon image orientation (portrait or landscape) but if you want to stretch only along height then you need to replace
if(image_height > image_width)
zoom_factor = container_height / image_height;
else
zoom_factor = container_width / image_width;
with
zoom_factor = container_height / image_height;
but I would recommend against it because it would not be able to handle landscape images.

Categories