object-fit: get resulting dimensions - javascript

When using the new CSS feature object-fit, how can I access the resulting dimensions that the browser has chosen by JavaScript?
So let's assume foo.jpg is 100x200 pixels. The browser page / viewport is 400px wide and 300px high. Then given this CSS code:
img.foo {
width: 100%;
height: 100%;
object-fit: contain;
object-position: 25% 0;
}
The browser would now show the image on the very top with correct aspect ration stretching to the very bottom on the second quarter from the left. This results in those image dimensions:
width: 150px
height: 300px
left: 62.5px
right: 212.5px
So what JavaScript call (jQuery allowed) would give me those numbers that I've calculated manually?
(Note: the CSS information themselves are not known by the JavaScript as the user could overwrite them and even add stuff like min-width)
To play with the code I've created a fiddle: https://jsfiddle.net/sydeo244/

Thanks to #bfred I didn't have to make the initial method.
Here is an extended (and rewritten) version of his, that does calculate the object-position values as well.
function getRenderedSize(contains, cWidth, cHeight, width, height, pos){
var oRatio = width / height,
cRatio = cWidth / cHeight;
return function() {
if (contains ? (oRatio > cRatio) : (oRatio < cRatio)) {
this.width = cWidth;
this.height = cWidth / oRatio;
} else {
this.width = cHeight * oRatio;
this.height = cHeight;
}
this.left = (cWidth - this.width)*(pos/100);
this.right = this.width + this.left;
return this;
}.call({});
}
function getImgSizeInfo(img) {
var pos = window.getComputedStyle(img).getPropertyValue('object-position').split(' ');
return getRenderedSize(true,
img.width,
img.height,
img.naturalWidth,
img.naturalHeight,
parseInt(pos[0]));
}
document.querySelector('#foo').addEventListener('load', function(e) {
console.log(getImgSizeInfo(e.target));
});
#container {
width: 400px;
height: 300px;
border: 1px solid blue;
}
#foo {
width: 100%;
height: 100%;
object-fit: contain;
object-position: 25% 0;
}
<div id="container">
<img id="foo" src="http://dummyimage.com/100x200/000/fff.jpg"/>
</div>
Side note
It appears that object-position can have more than 2 values, and when, you need to adjust (or add) which parameter returns the left position value

There's an npm package called intrinsic-scale that will calculate that for you, but it doesn't support the equivalent of object-position: https://www.npmjs.com/package/intrinsic-scale
This is the whole code:
// adapted from: https://www.npmjs.com/package/intrinsic-scale
function getObjectFitSize(contains /* true = contain, false = cover */, containerWidth, containerHeight, width, height){
var doRatio = width / height;
var cRatio = containerWidth / containerHeight;
var targetWidth = 0;
var targetHeight = 0;
var test = contains ? (doRatio > cRatio) : (doRatio < cRatio);
if (test) {
targetWidth = containerWidth;
targetHeight = targetWidth / doRatio;
} else {
targetHeight = containerHeight;
targetWidth = targetHeight * doRatio;
}
return {
width: targetWidth,
height: targetHeight,
x: (containerWidth - targetWidth) / 2,
y: (containerHeight - targetHeight) / 2
};
}
And the usage would be:
getObjectFitSize(true, img.width, img.height, img.naturalWidth, img.naturalHeight);

Here is a more comprehensive algorithm, tested, in order to determine the way the image is displayed on the screen.
var imageComputedStyle = window.getComputedStyle(image);
var imageObjectFit = imageComputedStyle.getPropertyValue("object-fit");
coordinates = {};
var imagePositions = imageComputedStyle.getPropertyValue("object-position").split(" ");
var horizontalPercentage = parseInt(imagePositions[0]) / 100;
var verticalPercentage = parseInt(imagePositions[1]) / 100;
var naturalRatio = image.naturalWidth / image.naturalHeight;
var visibleRatio = image.width / image.height;
if (imageObjectFit === "none")
{
coordinates.sourceWidth = image.width;
coordinates.sourceHeight = image.height;
coordinates.sourceX = (image.naturalWidth - image.width) * horizontalPercentage;
coordinates.sourceY = (image.naturalHeight - image.height) * verticalPercentage;
coordinates.destinationWidthPercentage = 1;
coordinates.destinationHeightPercentage = 1;
coordinates.destinationXPercentage = 0;
coordinates.destinationYPercentage = 0;
}
else if (imageObjectFit === "contain" || imageObjectFit === "scale-down")
{
// TODO: handle the "scale-down" appropriately, once its meaning will be clear
coordinates.sourceWidth = image.naturalWidth;
coordinates.sourceHeight = image.naturalHeight;
coordinates.sourceX = 0;
coordinates.sourceY = 0;
if (naturalRatio > visibleRatio)
{
coordinates.destinationWidthPercentage = 1;
coordinates.destinationHeightPercentage = (image.naturalHeight / image.height) / (image.naturalWidth / image.width);
coordinates.destinationXPercentage = 0;
coordinates.destinationYPercentage = (1 - coordinates.destinationHeightPercentage) * verticalPercentage;
}
else
{
coordinates.destinationWidthPercentage = (image.naturalWidth / image.width) / (image.naturalHeight / image.height);
coordinates.destinationHeightPercentage = 1;
coordinates.destinationXPercentage = (1 - coordinates.destinationWidthPercentage) * horizontalPercentage;
coordinates.destinationYPercentage = 0;
}
}
else if (imageObjectFit === "cover")
{
if (naturalRatio > visibleRatio)
{
coordinates.sourceWidth = image.naturalHeight * visibleRatio;
coordinates.sourceHeight = image.naturalHeight;
coordinates.sourceX = (image.naturalWidth - coordinates.sourceWidth) * horizontalPercentage;
coordinates.sourceY = 0;
}
else
{
coordinates.sourceWidth = image.naturalWidth;
coordinates.sourceHeight = image.naturalWidth / visibleRatio;
coordinates.sourceX = 0;
coordinates.sourceY = (image.naturalHeight - coordinates.sourceHeight) * verticalPercentage;
}
coordinates.destinationWidthPercentage = 1;
coordinates.destinationHeightPercentage = 1;
coordinates.destinationXPercentage = 0;
coordinates.destinationYPercentage = 0;
}
else
{
if (imageObjectFit !== "fill")
{
console.error("unexpected 'object-fit' attribute with value '" + imageObjectFit + "' relative to");
}
coordinates.sourceWidth = image.naturalWidth;
coordinates.sourceHeight = image.naturalHeight;
coordinates.sourceX = 0;
coordinates.sourceY = 0;
coordinates.destinationWidthPercentage = 1;
coordinates.destinationHeightPercentage = 1;
coordinates.destinationXPercentage = 0;
coordinates.destinationYPercentage = 0;
}
where image is the HTML <img> element and coordinates contains the following attributes, given that we consider sourceFrame being the rectangle defined by the image if it were totally printed, i.e. its natural dimensions, and printFrame being the actual displayed region, i.e. printFrame.width = image.width and printFrame.height = image.height:
sourceX: the horizontal position of the left-top point where the sourceFrame should be cut,
sourceY: the vertical position of the left-top point where the sourceFrame should be cut,
sourceWidth: how much horizontal space of the sourceFrame should be cut,
sourceHeight: how much vertical space of the sourceFrame should be cut,
destinationXPercentage: the percentage of the horizontal position of the left-top point on the printFrame where the image will be printed, relative to the printFrame width,
destinationYPercentage: the percentage of the vertical position of the left-top point on the printFrame where the image will be printed, relative to the printFrame height,
destinationWidthPercentage: the percentage of the printFrame width on which the image will be printed, relative to the printFrame width,
destinationHeightPercentage: the percentage of the printFrame height on which the image will be printed, relative to the printFrame height.
Sorry, the scale-down case is not handled, since its definition is not that clear.

Here is an updated piece of TypeScript code that handles all values including object-fit: scale-down and object-position both with relative, absolute, and keyword values:
type Rect = {
x: number;
y: number;
width: number;
height: number;
};
const dom2rect = (rect: DOMRect): Rect => {
const { x, y, width, height } = rect;
return { x, y, width, height };
};
const intersectRects = (a: Rect, b: Rect): Rect | null => {
const x = Math.max(a.x, b.x);
const y = Math.max(a.y, b.y);
const width = Math.min(a.x + a.width, b.x + b.width) - x;
const height = Math.min(a.y + a.height, b.y + b.height) - y;
if (width <= 0 || height <= 0) return null;
return { x, y, width, height };
};
type ObjectRects = {
container: Rect; // client-space size of container element
content: Rect; // natural size of content
positioned: Rect; // scaled rect of content relative to container element (may overlap out of container)
visible: Rect | null; // intersection of container & positioned rect
};
const parsePos = (str: string, ref: number): number => {
switch (str) {
case "left":
case "top":
return 0;
case "center":
return ref / 2;
case "right":
case "bottom":
return ref;
default:
const num = parseFloat(str);
if (str.endsWith("%")) return (num / 100) * ref;
else if (str.endsWith("px")) return num;
else
throw new Error(`unexpected unit object-position unit/value: '${str}'`);
}
};
const getObjectRects = (
image: HTMLImageElement | HTMLVideoElement
): ObjectRects => {
const style = window.getComputedStyle(image);
const objectFit = style.getPropertyValue("object-fit");
const naturalWidth =
image instanceof HTMLImageElement ? image.naturalWidth : image.videoWidth;
const naturalHeight =
image instanceof HTMLImageElement ? image.naturalHeight : image.videoHeight;
const content = { x: 0, y: 0, width: naturalWidth, height: naturalHeight };
const container = dom2rect(image.getBoundingClientRect());
let scaleX = 1;
let scaleY = 1;
switch (objectFit) {
case "none":
break;
case "fill":
scaleX = container.width / naturalWidth;
scaleY = container.height / naturalHeight;
break;
case "contain":
case "scale-down": {
let scale = Math.min(
container.width / naturalWidth,
container.height / naturalHeight
);
if (objectFit === "scale-down") scale = Math.min(1, scale);
scaleX = scale;
scaleY = scale;
break;
}
case "cover": {
const scale = Math.max(
container.width / naturalWidth,
container.height / naturalHeight
);
scaleX = scale;
scaleY = scale;
break;
}
default:
throw new Error(`unexpected 'object-fit' value ${objectFit}`);
}
const positioned = {
x: 0,
y: 0,
width: naturalWidth * scaleX,
height: naturalHeight * scaleY,
};
const objectPos = style.getPropertyValue("object-position").split(" ");
positioned.x = parsePos(objectPos[0], container.width - positioned.width);
positioned.y = parsePos(objectPos[1], container.height - positioned.height);
const containerInner = { x: 0, y: 0, width: container.width, height: container.height };
return {
container,
content,
positioned,
visible: intersectRects(containerInner, positioned),
};
};
You can adjust the return value to only output what you need.

Related

How to zoom or out image follow mouse position?use image element(scale、top、left)

I don’t know how to calculate the movement of the picture when it is zoom out or zoom in
Let the picture produce the feeling of zooming in or out from the mouse position, and have a natural excessive effect
The effect is similar to this
https://www.jacklmoore.com/wheelzoom/
https://github.com/jackmoore/wheelzoom
But I have to use "div" and "img" element
demo
https://i.imgur.com/hBRII3X.mp4
code is like this
const imageDiv = document.getElementById("imageDiv");
const image = document.getElementById("image");
const max = 10, min = 1, step = 0.2;
let startX = 0, startY = 0, offsetX = 0, offsetY = 0, holding = false;
function setImageTopAndLeft(image, left, top) {
const scale = parseFloat(image.dataset.scale); // current scale ratio
let currentLeft = (image.dataset.prevleft) ? parseInt(image.dataset.prevleft) : 0; // prev left value
let currentTop = (image.dataset.prevtop) ? parseInt(image.dataset.prevtop) : 0; // prev top value
let { width: imageWidth, height: imageHeight } = image.getBoundingClientRect();
let { width: constraintWidth, height: constraintHeight } = imageDiv.getBoundingClientRect();
let maxOffsetLeft = 0; // left max value
let maxOffsetTop = 0; // top max value
if (scale > 1) {
maxOffsetLeft = (imageWidth - constraintWidth) / 2; // left max value
maxOffsetTop = (imageHeight - constraintHeight) / 2; // top max value
let offsetLeft = left + currentLeft; // offset left value
let offsetTop = top + currentTop; // offset top value
if (maxOffsetLeft > 0) {
if (offsetLeft >= maxOffsetLeft) {
offsetLeft = maxOffsetLeft;
} else if (offsetLeft <= -1 * maxOffsetLeft) {
offsetLeft = -1 * maxOffsetLeft;
} else {
offsetLeft = offsetLeft * 1;
}
image.style.left = offsetLeft + "px";
}
if (maxOffsetTop > 0) {
if (offsetTop >= maxOffsetTop) {
offsetTop = maxOffsetTop;
} else if (offsetTop <= -1 * maxOffsetTop) {
offsetTop = -1 * maxOffsetTop;
} else {
offsetTop = offsetTop * 1;
}
image.style.top = offsetTop + "px";
}
}
}
function wheel(event) {
let ratio = parseFloat(image.dataset.scale);
if (event.deltaY < 0) { // 放大
ratio = ratio + step;
}
if (event.deltaY > 0) { // 縮小
ratio = ratio - step;
}
if (ratio > max) {
ratio = max;
}
if (ratio < min) {
ratio = min
}
image.dataset.scale = ratio.toFixed(2);
image.style.transform = `scale(${ratio.toFixed(2)})`
}
image.onload = () => {
imageDiv.addEventListener("wheel", (event) => {
wheel(event);
})
imageDiv.addEventListener("mousedown", (event) => {
event.preventDefault();
holding = true;
startX = event.pageX;
startY = event.pageY;
})
imageDiv.addEventListener("mousemove", (event) => {
event.preventDefault();
if (holding) {
offsetX = event.pageX - startX;
offsetY = event.pageY - startY;
setImageTopAndLeft(image, offsetX, offsetY);
}
})
imageDiv.addEventListener("mouseup", (event) => {
event.preventDefault();
holding = false;
startX = 0;
startY = 0;
image.dataset.prevleft = parseInt(image.style.left);
image.dataset.prevtop = parseInt(image.style.top);
})
}
div {
margin: auto;
width: 800px;
height: 800px;
overflow: hidden;
display: flex;
align-items: center;
/* child image vertical center */
justify-items: center;
/* child image horizontal center */
justify-content: center;
background-color: blue;
}
img {
max-width: 100%;
/*Adapt to different proportions of pictures*/
max-height: 100%;
/*Adapt to different proportions of pictures*/
position: relative;
top: 0;
/*mouse move change this value or wheel zoom then follow mouse position change this value*/
left: 0;
transform: scale(1);
/*wheel zoom or out scale image*/
}
<div id="imageDiv">
<img data-prevleft="0" data-prevtop="0" data-scale="1" id="image"
src="https://images.unsplash.com/photo-1637249833220-d399eb65e802?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1470&q=80">
</div>
jsfiddle demo link: https://jsfiddle.net/0b6qaLgz/2/
sorry my english is bad
HTML
<div id="container">
<div id="slide">
<img src="https://iso.500px.com/wp-content/uploads/2014/07/big-one.jpg">
</div>
</div>
CSS
#container{
width:500px;
height:500px;
overflow:hidden;
}
#slide{
width:100%;
height:100%;
transition: transform .3s;
}
img{
width:auto;
height:auto;
max-width:100%;
}
JS
$(document).ready(function (){
var scroll_zoom = new ScrollZoom($('#container'),4,0.5)
})
function ScrollZoom(container,max_scale,factor){
var target = container.children().first()
var size = {w:target.width(),h:target.height()}
var pos = {x:0,y:0}
var zoom_target = {x:0,y:0}
var zoom_point = {x:0,y:0}
var scale = 1
target.css('transform-origin','0 0')
target.on("mousewheel DOMMouseScroll",scrolled)
function scrolled(e){
var offset = container.offset()
zoom_point.x = e.pageX - offset.left
zoom_point.y = e.pageY - offset.top
e.preventDefault();
var delta = e.delta || e.originalEvent.wheelDelta;
if (delta === undefined) {
//we are on firefox
delta = e.originalEvent.detail;
}
delta = Math.max(-1,Math.min(1,delta)) // cap the delta to [-1,1] for cross browser consistency
// determine the point on where the slide is zoomed in
zoom_target.x = (zoom_point.x - pos.x)/scale
zoom_target.y = (zoom_point.y - pos.y)/scale
// apply zoom
scale += delta*factor * scale
scale = Math.max(1,Math.min(max_scale,scale))
// calculate x and y based on zoom
pos.x = -zoom_target.x * scale + zoom_point.x
pos.y = -zoom_target.y * scale + zoom_point.y
// Make sure the slide stays in its container area when zooming out
if(pos.x>0)
pos.x = 0
if(pos.x+size.w*scale<size.w)
pos.x = -size.w*(scale-1)
if(pos.y>0)
pos.y = 0
if(pos.y+size.h*scale<size.h)
pos.y = -size.h*(scale-1)
update()
}
function update(){
target.css('transform','translate('+(pos.x)+'px,'+(pos.y)+'px) scale('+scale+','+scale+')')
}
}
FIDDLE

ToastUI Image Editor - resize the images according to the container

I'm trying to adapt this image editor, so that it has a predetermined size of the canvas and that it doesn't change size according to the resolution of the image that is loaded. I wish it had a width of 300px and a height of 90px ...
I would like this size to always remain the same, and the image that is loaded, if anything, is cropped, but the size at download will always remain an image of 300px x 90px.
and that these dimensions remain the same even if I upload an image of 1800px x 2000px.
I use this code to initialize the component:
imageEditor = new tui.ImageEditor('#tui-image-editor-container', {
includeUI: {
loadImage: {
path: 'img/wallpaper.png',
name: 'wallpaper'
},
theme: blackTheme, // or whiteTheme
menu: ['crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask', 'filter'],
initMenu: 'filter',
imageSize: {
oldWidth: "0",
oldHeight: "0",
newWidth: "300",
newHeight: "90"
},
uiSize: {
width: '100%',
height: '500px'
},
menuBarPosition: 'bottom'
},
cssMaxWidth: 300,
cssMaxHeight: 90,
selectionStyle: {
cornerSize: 5,
rotatingPointOffset: 70
}
});
Is there anyone who knows how this can be achieved?
After several tests I solved it in this way, definitely not the best way but for the moment it works well for me, I publish an answer, I don't know maybe it can help someone, or simply give ideas ...
I initialized the component like this:
imageEditor = new tui.ImageEditor('#UBY_tui-image-editor-container', {
includeUI: {
loadImage: {
path: 'img/wallpaper.png',
name: 'wallpaper'
},
theme: blackTheme, // or whiteTheme
menu: ['crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask', 'filter'],
initMenu: 'filter',
uiSize: {
width: '100%',
height: '400px'
},
menuBarPosition: 'bottom'
},
selectionStyle: {
cornerSize: 5,
rotatingPointOffset: 70
}
});
A small change to the .css like this:
.tui-image-editor {
width: 300px;
height: 90px;
overflow: hidden;
}
.tui-image-editor-container {
margin: 0;
padding: 0;
box-sizing: border-box;
min-height: 300px;
height: 100%;
position: relative;
background-color: #282828;
overflow: hidden;
letter-spacing: 0.3px;
}
all this, however, still does not solve my problem of saving the image in the established dimensions, and therefore when saving the image, I did these operations ... I call this function to generate a new canvas and then save the image with the desired size:
function uby_resize_imageEditor(_this, _width, _hight, callback) {
try {
var tempCanvas = document.createElement("CANVAS")
tempCanvas.width = _width;
tempCanvas.height = _hight;
var ctx = tempCanvas.getContext('2d');
var img = new Image();
img.src = _this;
img.onload = function () {
var offsetX = 0.5; // center x
var offsetY = 0.5; // center y
drawImageProp(ctx, img, 0, 0, _width, _hight, offsetX, offsetY);
var dataURL = tempCanvas.toDataURL();
callback(dataURL)
};
} catch (error) {
errori_inSave = true
}
}
/**
* By Ken Fyrstenberg Nilsen
*
* drawImageProp(context, image [, x, y, width, height [,offsetX, offsetY]])
*
* If image and context are only arguments rectangle will equal canvas
*/
function drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {
if (arguments.length === 2) {
x = y = 0;
w = ctx.canvas.width;
h = ctx.canvas.height;
}
// default offset is center
offsetX = typeof offsetX === "number" ? offsetX : 0.5;
offsetY = typeof offsetY === "number" ? offsetY : 0.5;
// keep bounds [0.0, 1.0]
if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0;
if (offsetX > 1) offsetX = 1;
if (offsetY > 1) offsetY = 1;
var iw = img.width,
ih = img.height,
r = Math.min(w / iw, h / ih),
nw = iw * r, // new prop. width
nh = ih * r, // new prop. height
cx, cy, cw, ch, ar = 1;
// decide which gap to fill
if (nw < w) ar = w / nw;
if (Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh; // updated
nw *= ar;
nh *= ar;
// calc source rectangle
cw = iw / (nw / w);
ch = ih / (nh / h);
cx = (iw - cw) * offsetX;
cy = (ih - ch) * offsetY;
// make sure source rectangle is valid
if (cx < 0) cx = 0;
if (cy < 0) cy = 0;
if (cw > iw) cw = iw;
if (ch > ih) ch = ih;
// fill image in dest. rectangle
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}
hope this can help someone who has this problem.

sorting objects vertically with margin space between them

I'm drawing a simple panel (using Phaser.Graphics) which will contain a list of pre-defined amount of list-entries (also drawn usingPhaser.Graphics).
The size of each list-entry is dynamically calculated to fit the panel size.
Width of my list-entries are (+-) same as panel's width.
To get the height, I'm dividing panel's height by the number of list-entries and their margins size.
The result is almost accurate, but I'm still getting either some extra or missing some pixels below the last list-entry. So I suppose my calculation isn't correct or missing something ...
var game = new Phaser.Game(400, 300, Phaser.AUTO, 'phaser-example', {
create: create
});
var panel = null;
var listItems = 3
var listEntries = [];
function create() {
createPanel(game.world.centerX, game.world.centerY, game.world.width - 50, game.world.height - 100);
createList(panel);
}
function createPanel(x, y, width, height) {
panel = game.make.graphics(0, 0);
panel.position.x = x - (width / 2);
panel.position.y = y - (height / 2);
panel.lineStyle(2, 0x999999, 0.4);
panel.beginFill(0x0d1a26, 0.6);
panel.drawRect(0, 0, width, height);
panel.endFill();
game.world.add(panel)
}
function createList(parent) {
let child;
let margin = 5;
let width = parent.width - parent.lineWidth; //// - borders width? ////
let height = (parent.height - ( listItems * (margin * 2) )) / listItems;
let centerX = ((parent.width - width) / 2) - 1; /// - left-border width? ///
let prev_pos = 0;
for (let e = 0, e_l = listItems; e < e_l; e++) {
listEntries[e] = this.game.make.graphics(0, 0);
createListEntry(listEntries[e], width, height);
child = parent.addChild(listEntries[e]);
child.position.x += centerX;
if (e > 0) {
child.position.y += prev_pos + (margin * 2);
} else {
child.position.y += prev_pos + margin;
}
prev_pos = child.position.y + height;
console.log(child.position.y)
}
}
function createListEntry(entry, width, height) {
entry.clear();
entry.lineStyle(2, 0x999999, 0.5);
entry.beginFill(0x0d1a26, 0.7);
entry.drawRect(0, 0, width, height);
entry.endFill();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/2.6.2/phaser.min.js"></script>
<html>
<head>
<style>
canvas {
position: relative;
margin: 0 auto;
}
</style>
</head>
<body>
</body>
</html>
Integers for pixels.
From memory Phaser (the version that was about 2-3 years ago) would use an optimisation in javascript of converting all rendering coordinates to integers.
If you give it doubles it will floor them (or round I can't remember which). As you have a fraction that you carry through your position prev_pos is not matching the actual placement of the panels.
The easy fix is to only give Phaser integers. In this case round up with prev_pos = Math.ceil(child.position.y + height);
Fixed?
Your code with the minor change commented.
var game = new Phaser.Game(400, 300, Phaser.AUTO, 'phaser-example', {
create: create
});
var panel = null;
var listItems = 3
var listEntries = [];
function create() {
createPanel(game.world.centerX, game.world.centerY, game.world.width - 50, game.world.height - 100);
createList(panel);
}
function createPanel(x, y, width, height) {
panel = game.make.graphics(0, 0);
panel.position.x = x - (width / 2);
panel.position.y = y - (height / 2);
panel.lineStyle(2, 0x999999, 0.4);
panel.beginFill(0x0d1a26, 0.6);
panel.drawRect(0, 0, width, height);
panel.endFill();
game.world.add(panel)
}
function createList(parent) {
let child;
let margin = 5;
let width = parent.width - parent.lineWidth; //// - borders width? ////
let height = (parent.height - ( listItems * (margin * 2) )) / listItems;
let centerX = ((parent.width - width) / 2) - 1; /// - left-border width? ///
let prev_pos = 0;
for (let e = 0, e_l = listItems; e < e_l; e++) {
listEntries[e] = this.game.make.graphics(0, 0);
createListEntry(listEntries[e], width, height);
child = parent.addChild(listEntries[e]);
child.position.x += centerX;
if (e > 0) {
child.position.y += prev_pos + (margin * 2);
} else {
child.position.y += prev_pos + margin;
}
// The new expression
prev_pos = Math.ceil(child.position.y + height);
//.........^^^^^^^^^^.........................^
// the added code.
}
}
function createListEntry(entry, width, height) {
entry.clear();
entry.lineStyle(2, 0x999999, 0.5);
entry.beginFill(0x0d1a26, 0.7);
entry.drawRect(0, 0, width, height);
entry.endFill();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/2.6.2/phaser.min.js"></script>
<html>
<head>
<style>
canvas {
position: relative;
margin: 0 auto;
}
</style>
</head>
<body>
</body>
</html>

Save only a certain part of an HTML canvas

Is it possible to save or export only a certain part of the canvas rather than the whole canvas?
http://i.stack.imgur.com/hmvYh.jpg
At the moment, when I save the file I get the composition plus the transparent background (light blue in example above) of the entire canvas element I have on the site. What I would like to get is only the gray region (which could be made up of several images and text elements).
Yes you can. Here is the JSFiddle.
First, you need to take a clipping of the image in your canvas. This is pretty simple. I made another canvas (a hidden one) and used the context.drawImage
var hidden_ctx = hidden_canvas.getContext('2d');
hidden_ctx.drawImage(
MainCanvas,
startClippingX,
startClippingY,
clippingWidth,
clippingHeight,
pasteX,
pasteY,
pasteWidth,
pasteHeight
);
Now, we need the Data URL from this canvas so we can download the contents. For this, we will use the canvas.toDataURL method.
var data_url = hidden_canv.toDataURL("image/png");
Now, all we need to do is make a download link (an a element with an href attribute of our data_url) and we're done!
Suppose you have a canvas called oldCanvas and you want to save a rectangular area of width w and height h with its upper left corner at x, y.
Start by making a new canvas element of width w and height h:
var newCanvas = document.createElement('canvas');
newCanvas.width = w;
newCanvas.height = h;
Now copy the rectangular area to the new canvas:
var newContext = newCanvas.getContext('2d');
newContext.drawImage(oldCanvas, x, y, w, h, 0, 0, w, h);
Finally, save the new canvas using toDataUrl() or whatever method you were using previously to save a whole canvas.
For example, you can make an image out of the new canvas:
var newImage = document.createElement('img');
newImage.src = newCanvas.toDataURL();
Then append the new image to the web page:
document.body.appendChild(newImage);
Or maybe you have a container div that you want to append it to.
In any case, once the image is in the document, you can right-click on it and save it as usual.
I've implemented this approach in the following snippet. When you run it, a canvas will be randomly painted. Click and drag on the canvas to select a region that you want to download. A new, downloadable image appears at right.
// Returns a random RGB string (RGBA if alpha is true).
function randomColor(alpha) {
var rgb = [
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255),
Math.floor(Math.random() * 255)
];
if (alpha) {
rgb.push(Math.random());
}
return 'rgb' + (alpha ? 'a' : '') + '(' + rgb.join(', ') + ')';
}
// Makes a random picture for use in the demonstration.
function makeCanvas() {
var canvas = document.getElementById('oldCanvas'),
context = canvas.getContext('2d'),
width = canvas.width = 400,
height = canvas.height = 500;
context.fillStyle = randomColor();
context.fillRect(0, 0, width, height);
for (var i = 0; i < 200; ++i) {
var x = Math.floor(Math.random() * width),
y = Math.floor(Math.random() * height),
w = Math.floor(Math.random() * width/5),
h = Math.floor(Math.random() * height/5);
context.fillStyle = randomColor(true);
if (Math.floor(Math.random() * 2) === 0) {
context.fillRect(x - w / 2, y - h / 2, w, h);
} else {
context.beginPath();
context.arc(x, y, w, 0, 2 * Math.PI);
context.closePath();
context.fill();
}
}
return canvas;
};
window.onload = function () {
var oldCanvas = makeCanvas(),
oldContext = oldCanvas.getContext('2d'),
targetImage = document.getElementById('targetImage'),
downloadContainer = document.getElementById('downloadContainer'),
selectCanvas = document.getElementById('selectCanvas'),
selectContext = selectCanvas.getContext('2d'),
width = selectCanvas.width = oldCanvas.width,
height = selectCanvas.height = oldCanvas.height;
selectContext.fillStyle = '#000';
downloadContainer.style.left = width + 25 + 'px';
var clipCanvas = document.createElement('canvas'),
clipContext = clipCanvas.getContext('2d');
downloadContainer.appendChild(clipCanvas);
selectCanvas.onmousedown = function (event) {
var x0 = Math.max(0, Math.min(event.clientX, width)),
y0 = Math.max(0, Math.min(event.clientY, height));
targetImage.style.display = 'none';
function update(event) {
var x = Math.max(0, Math.min(event.clientX, width)),
y = Math.max(0, Math.min(event.clientY, height)),
dx = x - x0, w = Math.abs(dx),
dy = y - y0, h = Math.abs(dy);
selectContext.clearRect(0, 0, width, height);
selectContext.fillRect(x0, y0, dx, dy);
clipCanvas.width = w;
clipCanvas.height = h;
if (w*h == 0) {
downloadContainer.style.visibility = 'hidden';
} else {
downloadContainer.style.visibility = 'visible';
clipContext.drawImage(oldCanvas,
x0 + Math.min(0, dx), y0 + Math.min(0, dy), w, h,
0, 0, w, h);
downloadContainer.style.visibility = (w*h == 0 ? 'hidden' : 'visible');
downloadContainer.style.top = Math.min(y0, y) + 'px';
}
};
update(event);
selectCanvas.onmousemove = update;
document.onmouseup = function (event) {
selectCanvas.onmousemove = undefined;
document.onmouseup = undefined;
targetImage.src = clipCanvas.toDataURL();
targetImage.style.display = 'block';
};
};
};
body, div, canvas, img {
margin: 0;
padding: 0;
}
#targetImage {
display: none;
position: absolute;
left: 0;
top: 0;
}
canvas {
display: block;
}
#oldCanvas, #selectCanvas, #downloadContainer {
position: fixed;
}
#downloadContainer {
visibility: hidden;
}
#downloadContainer .label {
position: absolute;
width: 500px;
bottom: -20px;
font-family: sans-serif;
font-size: 17px;
color: #444;
}
#selectCanvas {
opacity: 0.5;
cursor: default;
}
<canvas id="oldCanvas"></canvas>
<canvas id="selectCanvas"></canvas>
<div id="downloadContainer">
<div class="label"> right-click above to download this image </div>
<img id="targetImage">
</div>

Image fill (not stretch) using JavaScript

I would like to fill a div with an image (but not stretch) just like in this post CSS Image size, how to fill, not stretch? but instead of using CSS I need to calculate values using JavaScript.
This is what I have:
image.onload = ()=> {
var ratio: number = image.width / image.height;
if (ratio > 1) {
image.height = this._height;
image.width = ratio * this._height;
image.style.left = -((image.width - this._width) / 2) + "px";
} else {
ratio = 1 / ratio;
image.width = this._width;
image.height = ratio * this._width;
image.style.top = -((image.height - this._height) / 2) + "px";
}
};
this is the div and image is a normal Image().
It works in most cases but not when for example this._width < ratio*this._height.
How can I get the algorithm to work for all cases? I know it's pretty simple but I can't get it to work.
I think the problem is that you compare ratio with 1, but you should compare it with div's ratio:
image.onload = ()=> {
var imgRatio: number = image.width / image.height,
divRatio: number = this._width / this._height;
if (imgRatio > divRatio) {
image.height = this._height;
image.width = this._height * imgRatio;
image.style.left = -((image.width - this._width) / 2) + "px";
} else {
image.width = this._width;
image.height = this._width / imgRatio;
image.style.top = -((image.height - this._height) / 2) + "px";
}
};
Im not sure if this will helps but this is the function that I have used in the past for setting image sizes for the canvas element.
The image will take up the whole element without skewing it.
function setCanvasImage(canvas, source) {
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var context = canvas.getContext('2d');
var image = new Image();
image.src = source;
image.onload = function () {
var sourceWidth = canvasWidth / canvasHeight * image.height;
var sourceX = (image.width - sourceWidth) / 2;
if(sourceX > 0) {
var sourceY = 0;
var sourceHeight = image.height;
} else {
var sourceX = 0;
var sourceWidth = image.width;
var sourceHeight = canvasHeight / canvasWidth * image.width;
var sourceY = (image.height - sourceHeight) / 2;
}
//placing
var destinationX = 0;
var destinationY = 0;
var destinationWidth = canvas.width;
var destinationHeight = canvas.height;
context.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, destinationX, destinationY, destinationWidth, destinationHeight);
}
}
There are 2 scale values that you need to calculate, the scale value that makes the image as wide as the container, and the scale value that makes the image as tall as the container. To scale the image to fit inside this container, you choose the smaller of these scales, and to scale the image to extend beyond the container, you choose the largest. You don't care about the ratio between the width and height of the image. Example of fitting the image inside the container and centring:
var xScale = _width/img.width; // Scale by this much to fit x
var yScale = _height/img.height; // Scale by this much to fit y
var width = _width;
var height = _height;
var top = 0;
var left = 0;
if (xScale !== yScale) {
// Choose the smaller scale. To make the image extend, make this >
if (xScale < yScale) {
height = Math.round(xScale * height);
top = Math.round((_height - height) / 2);
} else {
width = Math.round(yScale * width);
left = Math.round((_width - width) / 2);
}
}
img.width = width;
img.height = height;
img.top = top + "px";
img.left = left + "px";

Categories

Resources