Trying to make use of Jcrop and serverside image resizing with scala Scrimage lib - scala

I'm trying to combine jcrop and scrimage but I'm having trouble in understanding
the documentation of scrimage.
The user uploads an image. When the upload is done the user is able choose a fixed
area to crop with Jcrop:
upload.js
$(function () {
$('#fileupload').fileupload({
dataType: 'json',
progress: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$("#progress").find(".progress-bar").css(
"width",
progress + "%"
);
},
done: function (e, data) {
$("#cropArea").empty();
var cropWidth = 210;
var cropHeight = 144;
if(data.result.status == 200) {
var myImage = $("<img></img>", {
src: data.result.link
}).appendTo('#cropArea');
var c = myImage.Jcrop({
allowResize: false,
allowSelect: false,
setSelect:[0,0,cropWidth,cropHeight],
onSelect: showCoords
});
}
}
});
});
Example:
When the user is satisfied the coordinates will be posted to the server and there is where the magic should happen.
Controller:
def uploadFile = Action(multipartFormDataAsBytes) { request =>
val result = request.body.files.map {
case FilePart(key, filename, contentType, bytes) => {
val coords = request.body.dataParts.get("coords")
val bais = new ByteArrayInputStream(bytes)
Image(bais).resize(magic stuff with coords)
Ok("works")
}
}
result(0)
}
If i read the docs for scrimage and resize:
Resizes the canvas to the given dimensions. This does not scale the
image but simply changes the dimensions of the canvas on which the
image is sitting. Specifying a larger size will pad the image with a
background color and specifying a smaller size will crop the image.
This is the operation most people want when they think of crop.
But when trying to implement resize with an inputstream Image(is).resize() I'm not sure I how should do this. Resize takes a scaleFactor, position and color... I guess I should
populate the position with the coords I get from jcrop??, and what do I do with the scaleFactor? Anybody got a good example of how to do this this?
Thank you for two great libs!

Subimage is what you want. That lets you specify coordinates rather than offsets.
So simply,
val image = // original
val resized = image.subimage(x,y,w,h) // you'll get these from jcrop somehow

Related

How to do a preprocessing on tiles of a TileLayer before displaying them on Leaflet?

I have a large image that I broke up in 256x256 tiles using gdal2tiles.py.
I display it on leaflet as a L.tileLayer. It works fine.
Now, I'd like to preprocess the 256x256 tiles before they are rendered in tileLayer.
I need to apply an algorithm on these stored tiles, and the algorithm generates tiles of same size, but with different content. It can looks like changing stored tiles content dynamically.
Is it possible to replace tiles that are in TileLayer with processed tiles ?
How should I proceed ?
I would like to process these tiles only once, so I guess I should take advantage of caching.
#IvanSanchez thank you for your answer.
I created a new tileLayer allowing to call a rest API doing predictions on tiles (taking and returning a base64-encoded PNG image).
/*
* L.TileLayer.Infer, inspired by L.TileLayer.PixelFilter (https://github.com/GreenInfo-Network/L.TileLayer.PixelFilter/)
*/
L.tileLayerInfer = function (url, options) {
return new L.TileLayer.Infer(url, options);
}
L.TileLayer.Infer = L.TileLayer.extend({
// the constructor saves settings and throws a fit if settings are bad, as typical
// then adds the all-important 'tileload' event handler which basically "detects" an unmodified tile and performs the pxiel-swap
initialize: function (url, options) {
L.TileLayer.prototype.initialize.call(this, url, options);
// and add our tile-load event hook which triggers us to do the infer
this.on('tileload', function (event) {
this.inferTile(event.tile);
});
},
// extend the _createTile function to add the .crossOrigin attribute, since loading tiles from a separate service is a pretty common need
// and the Canvas is paranoid about cross-domain image data. see issue #5
// this is really only for Leaflet 0.7; as of 1.0 L.TileLayer has a crossOrigin setting which we define as a layer option
_createTile: function () {
var tile = L.TileLayer.prototype._createTile.call(this);
tile.crossOrigin = "Anonymous";
return tile;
},
// the heavy lifting to do the pixel-swapping
// called upon 'tileload' and passed the IMG element
// tip: when the tile is saved back to the IMG element that counts as a tileload event too! thus an infinite loop, as wel as comparing the pixelCodes against already-replaced pixels!
// so, we tag the already-swapped tiles so we know when to quit
// if the layer is redrawn, it's a new IMG element and that means it would not yet be tagged
inferTile: function (imgelement) {
// already processed, see note above
if (imgelement.getAttribute('data-InferDone')) return;
// copy the image data onto a canvas for manipulation
var width = imgelement.width;
var height = imgelement.height;
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var context = canvas.getContext("2d");
context.drawImage(imgelement, 0, 0);
// encode image to base64
var uri = canvas.toDataURL('image/png');
var b64 = uri.replace(/^data:image.+;base64,/, '');
var options = this.options;
// call to Rest API
fetch('/api/predict', {
method: 'POST',
mode: 'no-cors',
credentials: 'include',
cache: 'no-cache',
headers: {
'Content-type': 'application/json',
'Accept': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
'image': [b64]
})
})
.then((response) => response.json())
.then((responseJson) => {
// Perform success response.
const obj = JSON.parse(responseJson);
image = "data:image/png;base64," + obj["predictions"][0];
var img = new Image();
img.onload = function() {
// draw retrieve image on tile (replace tile content)
context.globalAlpha = options.opacity
context.drawImage(img, 0, 0, canvas.width, canvas.height);
}
img.src = image;
imgelement.setAttribute('data-InferDone', true);
imgelement.src = image;
})
.catch((error) => {
console.log(error)
});
}
});

html2canvas toDataURL(image/png") return poor image quality

I tried to use html2canvas to get image screenshot byte from website. However, the screenshot result ended with poor resolution. Looking for advice to improve screenshot quality. Thanks.
How about something like this:
var $wrapper = $("#yourDiv");
setSize($wrapper, "2000px", "20pt");
html2canvas($wrapper, {
onrendered: function (canvas) {
var a = document.createElement('a');
a.href = canvas.toDataURL("image/jpg");
a.download = 'filename.jpg';
a.click();
setSize($wrapper, "1000px", "10pt");
}
});
function setSize(dv, width, fontsize) {
dv[0].style.width = width;
dv[0].style.fontSize = fontsize;
}
This resizes the div and font to bigger size and then shrinks back afterwards.

leaftletjs-adding points dynamically and draw line string

I am trying to draw the path of a flight using leafletjs and geojson. I'll be getting the geometry from a stream.
this is what I have done so far:
let index = 0;
let geoJsonLayer;
let intervalFn = setInterval(function () {
let point = trackData.features[index++];
if(point) {
let coords = point.geometry.coordinates;
coords.pop();
coords.reverse();
geoFeature.geometry.coordinates.push(coords);
if(map.hasLayer(geoJsonLayer)) map.removeLayer(geoJsonLayer);
geoJsonLayer = L.geoJson(geoFeature, {
onEachFeature: (feature, layer) => {
const content = feature.properties.title;
layer.bindPopup(content);
}
});
geoJsonLayer.addTo(map);
// console.log(coords);
} else {
clearInterval(intervalFn);
}
}, 100);
setInterval is to simulate the part whereby I get the geometry from a stream.
now when a user clicks on the path I need to show some properties of the path, and I am trying to use the onEachFeature for that, but its not working correctly.
I suspect its because I am removing the layers (I did this to improve the performance)
Is there any other better ways to do what I am trying to achieve ?
You should probably try addLatLng()
Adds a given point to the polyline.
Your geoFeature sounds to be a single Feature, so your geoJsonLayer will contain a single layer (polyline):
let myPolyline;
geoJsonLayer.eachLayer(function (layer) {
myPolyline = layer; // Will be done only once actually.
});
// When you receive a new point…
myPolyline.addLatLng([lat, lng]);
With this you should not have to remove your layers every time.
The popup should therefore stay open, if it is shown.
Demo: https://jsfiddle.net/3v7hd2vx/265/ (click on the button to add new points)

Preload Images for Photoswipe Gallery

So I have an array of images I want to load into a gallery using Photoswipe, but I'm having trouble predefining the image width and height. Specifically, I think I need to preload the images
Here's my JS to render the page, here I'm defining slides and listing as a local variable for the ejs page to use:
var sizeOf = require('image-size');
var url = require('url');
var http = require('http');
var slideshow = [];
for(var i = 0; i < listing.listing_images.length; i++) {
var image = listing.listing_images[i];
var width, height = 0;
var imgUrl = image.url;
var options = url.parse(imgUrl);
http.get(options, function (response) {
var chunks = [];
response.on('data', function (chunk) {
chunks.push(chunk);
}).on('end', function() {
var buffer = Buffer.concat(chunks);
**height = sizeOf(buffer).height;
width = sizeOf(buffer).width;**
});
});
var item = {
src: image.url,
h: height,
w: width
};
slideshow.push(item);
}
res.render('example.ejs', {
listing: listing,
slides: slideshow
});
And here is the script in the ejs page :
<% var slides = locals.slides %>
<script>
$('document').ready(function() {
var pswpElement = document.querySelectorAll('.pswp')[0];
// build items array using slideshow variable
var items = <%- JSON.stringify(slides) %>;
console.log(items);
// grab image
if (items.length > 0) {
// define options (if needed)
var options = {
// optionName: 'option value'
// for example:
index: 0 // start at first slide
};
// Initializes and opens PhotoSwipe
var gallery = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, items, options);
gallery.init();
}
</script>
Basically what's happening is the array of photoswipe items is being passed in fine, but the width and height aren't set until photoswipe initializes and triggers the img to load. So the images don't show, because their height and width aren't set yet.
Is there a way to trigger the loading of the images in the slideshow array so that the width & height are set before passing to Photoswipe? I've also tried seeing if I could just set them initially to 0, and then try and update the height and width later and try to force photoswipe to reload, but photoswipe doesn't recognize the image's new height/width.
Sorry if any of this is unclear/muddled with ejs nonsense, feel free to ask anything and I'd love to clarify.
Thanks
Ended up solving this leveraging the API:
gallery.listen('gettingData', function(index, item) {
// index - index of a slide that was loaded
// item - slide object
var img = new Image();
img.src = item.src;
item.h = img.height;
item.w = img.width;
});
gallery.invalidateCurrItems();
// updates the content of slides
gallery.updateSize(true);
If anyone happens to be reading this and there's a better way to read image size without creating a new img, or optimize this I'd love suggestions. :)

JCrop: previous image not updated?

I developed a small JCrop file upload app; here is my code:
function createCropImage(event)
{
//alert(event.target.result);
document.getElementById("Imgpreview").src = event.target.result;
var img2 = document.getElementById("Imgpreview1").src = event.target.result;
// Create variables (in this scope) to hold the API and image size
var jcrop_api, boundx, boundy;
$('#Imgpreview1').Jcrop({
onChange: updatePreview,
onSelect: updatePreview,
aspectRatio: 1
},function(){
// Use the API to get the real image size
var bounds = this.getBounds();
boundx = bounds[0];
boundy = bounds[1];
// Store the API in the jcrop_api variable
jcrop_api = this;
});
function updatePreview(c)
{
$('#Xcoardinate').val( Math.round(c.x));
$('#Ycoardinate').val( Math.round(c.y));
$('#width').val( Math.round(c.w));
$('#height').val( Math.round(c.h));
if (parseInt(c.w) > 0)
{
var rx = 100 / c.w;
var ry = 100 / c.h;
$('#Imgpreview').css({
width: Math.round(rx * boundx) + 'px',
height: Math.round(ry * boundy) + 'px',
marginLeft: '-' + Math.round(rx * c.x) + 'px',
marginTop: '-' + Math.round(ry * c.y) + 'px'
});
}
};
}
Here Imgpreview is the preview image and Imgpreview1 is the source image. I first select an image through the browse button:
<input type="file" size="45" id="photoUploadElmt" name="upload" onchange="previewImg()" style="width:430px;"/>
The original image (Imgpreview1) and preview image (Imgpreview) are showing fine, but if I select another image, the preview image is correct but in place of Imgpreview1 I see the older image.
If I put following code in comments, then images are displayed properly but I lose the JCrop instance:
$('#Imgpreview1').Jcrop({
onChange: updatePreview,
onSelect: updatePreview,
aspectRatio: 1
},function(){
// Use the API to get the real image size
var bounds = this.getBounds();
boundx = bounds[0];
boundy = bounds[1];
// Store the API in the jcrop_api variable
jcrop_api = this;
});
The destroy method is unreliable, so create a custom one as in this similar question