Display images from Node (MERN) - node.js

I’m building a MERN App and trying to display image saved in MongoDB on the fronted. I use Multer and Sharp to process images when saved to database like so:
const buffer = await sharp(req.file.path)
.resize({ width: 160, height: 160 })
.png()
.toBuffer();
group.groupImgUrl = Buffer.from(buffer, "binary").toString("base64");
await group.save();

when I console.log(group.groupImgUrl), it gives me the following output: new Binary(Buffer.from("6956….73d3d", "hex"), 0)
On the frontend side, I receive the groupImgUrl in different formats, sometimes in typed array, sometimes in string. when the response is in shape: groupImgUrl: { type: Buffer; data: Array }, I can transform the typed array and display it properly:
let base64Str = "";
if ("data" in group.groupImgUrl) {
response.group.groupImgUrl.data.forEach((index: number) => {
base64Str += String.fromCharCode(index)
})
}
<img src = `data:image/png;base64,${src}` />
However, when I receive the groupImgUrl in string format, it’s somehow tampered and cannot display correctly using src = data:image/png;base64,${src}
My question is, how come I receive the same image in different formats, and how to display them properly?

Related

How to center continued text using PdfKit?

I have this code:
const PDFDocument = require("pdfkit");
const QRCode = require("qrcode");
const fs = require("fs");
const exec = async () => {
const doc = new PDFDocument({ layout: "landscape" });
doc.pipe(fs.createWriteStream("output.pdf"));
for (let pageNumber = 1; pageNumber <= 1000; pageNumber++) {
const url = await QRCode.toDataURL("I am a url!");
doc
.image(url, 10, 100, {
width: 420,
height: 420,
align: "center",
valign: "center",
})
doc
.font("Helvetica")
.fontSize(50)
.fillColor("#000")
.text(`Item `, 465, 200, { continued: true })
.fontSize(55)
.font("Courier-Bold")
.fillColor("#1b83c5")
.text(`${pageNumber}`);
doc
.font("Helvetica-Bold")
.fontSize(40)
.fillColor("#000")
.text("Order and Pay", 420, 320);
doc.addPage();
}
doc.end();
};
exec();
Which would produce something like this:
It looks centered and all, but as pages increase it will no longer be centered since the numbers are fixed.
I saw in the docs that there's an align property, but the docs didn't explain how to handle continued text.
Any working examples?
Maybe it's a little late, but I found a solution battling with the same problem:
First you need to create a constant with the width of the container you want to center your text in: (this value you have to calculate or invent, but it's easy to do that)
const containerWidth = 100 // as an example
Then, you need to create a variable that contains a plain string that contains all the text you want to center, in your example:
var appendedText = `Item ${pageNumber}`
To finish, you need to use pdf-kit's function: widthOfString, and add the text in the document as follows:
const xOffset = 465 // The original X offset value
doc
.text(`Item `, xOffset + (containerWidth / 2) - (doc.widthOfString(appendedText) / 2), 200, { continued: true })
.text(`${pageNumber}`);
Removed the text styling lines for clarity, but you have to add them later.

PDFKit split text into two equal columns while using for loop

Im trying to use PDFKit to generate a simple pdf, for the most part the pdf works but albeit in a very non useful way, what i have is a deck building API that takes in a number of cards, each of these objects i want to export to a pdf, its as simple as displaying their name, but as it is, the pdf only renders one card at a time, and only on one line, what id like to happen is to get it to split the text into columns so itd look similar to this.
column 1 | column 2
c1 c8
c2 c9
c3 c10
c4 c(n)
here is my code so far,
module.exports = asyncHandler(async (req, res, next) => {
try {
// find the deck
const deck = await Deck.findById(req.params.deckId);
// need to sort cards by name
await deck.cards.sort((a, b) => {
if (a.name < b.name) {
return -1;
} else if (a.name > b.name) {
return 1;
} else {
return 0;
}
});
// Create a new PDF document
const doc = new PDFDocument();
// Pipe its output somewhere, like to a file or HTTP response
doc.pipe(
fs.createWriteStream(
`${__dirname}/../../public/pdf/${deck.deck_name}.pdf`
)
);
// Embed a font, set the font size, and render some text
doc.fontSize(25).text(`${deck.deck_name} Deck List`, {
align: "center",
underline: true,
underlineColor: "#000000",
underlineThickness: 2,
});
// We need to create two columns for the cards
// The first column will be the card name
// The second column will continue the cards listed
const section = doc.struct("P");
doc.addStructure(section);
for (const card of deck.cards) {
doc.text(`${card.name}`, {
color: "#000000",
fontSize: 10,
columns: 2,
columnGap: 10,
continued: true,
});
}
section.end();
// finalize the PDF and end the response
doc.end();
res.status(200).json({ message: "PDF generated successfully" });
} catch (error) {
console.error(error);
res.status(500).json({
success: false,
message: `Server Error - ${error.message}`,
});
}
});
At Present this does generate a column order like i want, however theres and extreme caveat to this solution and that is, if the card text isnt very long, the next card will start on that same line, it'd be useful if i could find a way to make the text take up the full width of that row, but i havent seen anything to do that with.
I think the problem is that you're relying on PDFKit's text "flow" API/logic, and you're having problems when two cards are not big enough to flow across your columns and you get two cards in one column.
I'd say that what you really want is to create a table—based on your initial text sample.
PDFKit doesn't have a table API (yet), so you'll have to make one up for yourself.
Here's an approach where you figure out the dimensions of things:
the page size
the size of your cells of text (either manually choose for yourself, or use PDFKit to tell you how big some piece of text is)
margins
Then you use those sizes to calculate how many rows and columns of your text can fit on your page.
Finally you iterate of over columns then rows for each page, writing text into those column-by-row "coordinates" (which I track through "offsets" and use to calculate the final "position").
const PDFDocument = require('pdfkit');
const fs = require('fs');
// Create mock-up Cards for OP
const cards = [];
for (let i = 0; i < 100; i++) {
cards.push(`Card ${i + 1}`);
}
// Set a sensible starting point for each page
const originX = 50;
const originY = 50;
const doc = new PDFDocument({ size: 'LETTER' });
// Define row height and column widths, based on font size; either manually,
// or use commented-out heightOf and widthOf methods to dynamically pick sizes
doc.fontSize(24);
const rowH = 50; // doc.heightOfString(cards[cards.length - 1]);
const colW = 150; // doc.widthOfString(cards[cards.length - 1]); // because the last card is the "longest" piece of text
// Margins aren't really discussed in the documentation; I can ignore the top and left margin by
// placing the text at (0,0), but I cannot write below the bottom margin
const pageH = doc.page.height;
const rowsPerPage = parseInt((pageH - originY - doc.page.margins.bottom) / rowH);
const colsPerPage = 2;
var cardIdx = 0;
while (cardIdx < cards.length) {
var colOffset = 0;
while (colOffset < colsPerPage) {
const posX = originX + (colOffset * colW);
var rowOffset = 0;
while (rowOffset < rowsPerPage) {
const posY = originY + (rowOffset * rowH);
doc.text(cards[cardIdx], posX, posY);
cardIdx += 1;
rowOffset += 1;
}
colOffset += 1;
}
// This is hacky, but PDFKit adds a page by default so the loop doesn't 100% control when a page is added;
// this prevents an empty trailing page from being added
if (cardIdx < cards.length) {
doc.addPage();
}
}
// Finalize PDF file
doc.pipe(fs.createWriteStream('output.pdf'));
doc.end();
When I run that I get a PDF with 4 pages that looks like this:
Changing colW = 250 and colsPerPage = 3:

How to crop image after upload Cloudinary?

How i can to crop image after upload and send edited response ulr to frontent?
I will be grateful for the answer
MY CODE:
const stream = cloudinary.uploader.upload_stream(
{
folder,
},
(error: UploadApiErrorResponse | undefined, result: UploadApiResponse | undefined): void => {
console.log(error, result)
if (result) {
resolve({
url: result.url,
size: Math.round(result.bytes / 1024),
height: result.height,
width: result.width,
})
} else {
reject(error)
}
}
)
streamifier.createReadStream(buffer).pipe(stream)
The most common method of integrating Cloudinary is that you upload the original file to your Cloudinary account and store the Upload API response to your database which contains the details for the image: https://cloudinary.com/documentation/image_upload_api_reference#sample_response
If you don't want to store the entire response, you should store at least the fields needed to create a URL for that image later: https://cloudinary.com/documentation/image_transformations#transformation_url_structure
(Which are public_id, resource_type, type, format, and timestamp) though strictly speaking, most of those are optional if your assets are images of type 'upload' - certainly you need the public_id though.
Then, in your frontend code, when adding the image to your page or to your application, you add transformation parameters when building the URL, asking that the image is returned with transformations applied to match it to where/how you're using the image.
A common option is to set the width and height to exactly match the image tag or image view, then apply automatic cropping if the aspect ratio of the original doesn't match, with the crop selection being automatic: https://cloudinary.com/documentation/resizing_and_cropping
A Javascript example to add those parameters, if the image should be 500x500 is:
cloudinary.url( public_id,
{
resource_type: 'image', //these are the defaults and can be ommitted
type: 'upload', //these are the defaults and can be ommitted
height: 500,
width: 500,
crop: 'lfill', // try to fill the requested width and height without scaling up, crop if needed
gravity: 'auto', // select which area to keep automatically
fetch_format: 'auto',
quality: 'auto',
format: 'jpg', // sets the file extension on the URL, and will convert to that format if needed, and no fetch_format was set to override that
});
The resulting URL will be something like: http://res.cloudinary.com/demo/image/upload/c_lfill,f_auto,g_auto,h_500,q_auto,w_500/sample.jpg

Tensorflow js server side classification with mobilenet and blazeface

I'm trying to use tensorflowjs with an already built model, at the beginning I had a problem with blazeface because it didn't find face on photo (that display just faces) and so I'm trying with mobilenet and same probleme the result are non sens. So pretty sur it came from the format of image I'm sending.
So this is my code:
module.exports = {
blazeface: require('#tensorflow-models/blazeface'),
mobilenet: require('#tensorflow-models/mobilenet'),
tf: require("#tensorflow/tfjs-node"),
fs: require("fs"),
sizeOf: require("image-size"),
async structureData(imageLink) {
let data = this.fs.readFileSync(imageLink);//return a buffer
const dimension = await this.sizeOf(imageLink);
const tensorflowImage = {
data: data,
width: dimension.width,
height: dimension.height
};
console.log(tensorflowImage)
return tensorflowImage;
},
async detectFace(imageLink) {
let image = await this.structureData(imageLink);
const model = await this./*blazeface*/mobilenet.load();
const returnTensors = false;
const predictions = await model.classify(image);
console.log(predictions);
}
}
So this code do not result of an error but the result are this =>
[
{
className: 'theater curtain, theatre curtain',
probability: 0.03815646469593048
},
{
className: 'web site, website, internet site, site',
probability: 0.0255243219435215
},
{ className: 'matchstick', probability: 0.02520526386797428 }
]
and with any photo (here it's a banana in white background).
So I'm pretty sur i need to rebuilt the structureData function but I don't know how ...
I also tried this with uint32array
async structureData(imageLink) {
let data = this.fs.readFileSync(imageLink);
data = new Uint32Array(data);
const dimension = await this.sizeOf(imageLink);
const tensorflowImage = {
data: data,
width: dimension.width,
height: dimension.height
};
console.log(tensorflowImage)
return tensorflowImage;
},
But I'm getting this error.
Error: pixels passed to tf.browser.fromPixels() must be either an HTMLVideoElement, HTMLImageElement, HTMLCanvasElement, ImageData in browser, or OffscreenCanvas, ImageData in webworker or {data: Uint32Array, width: number, height: number}, but was Object
remember that i'm using node and so I can't (or don't think I can) us HTMLimageelement
Thanks a lot :)
By using a tensor, a video or image element as parameter to the model, it will be able to do the classification.
let data = this.fs.readFileSync(imageLink);
tensor = tf.node.decodeImage(data, 3)
await model.classify(tensor)

Why does my for loop only goes through once when i call function inside it?

I got list of videos from API, it has list of urls fo thumbnail and i would like to combine thumbnails of each video to gif. When i loop through videos and don't generate gifs it goes through 5 times as expected, but if i include function that should generate gifs it only goes through once, without any errors. I have no idea what is happening
I'm using node.js, discord.js, get pixels and gif-encoder modules to generate thumbnails.
for(i=0;i<5;i++){
generateThumbnail(data[i].video.video_id,data[i].video.thumbs,function(){
var tags = '';
for(t=0;t<data[i].video.tags.length;t++){
tags = tags + data[i].video.tags[t].tag_name+', ';
}
fields = [
{name:data[i].video.title,
value:value},
{name:'Tags',
value:tags}
]
msg.channel.send({embed: {
color: 3447003,
thumbnail: {
"url": ""
},
fields: fields,
}});
});
}
function generateThumbnail(id,images,fn){
var pics = [];
console.log(id)
var file = require('fs').createWriteStream(id+'.gif');
var gif = new GifEncoder(images[0].width, images[0].height);
gif.pipe(file);
gif.setQuality(20);
gif.setDelay(1000);
gif.setRepeat(0)
gif.writeHeader();
for(i=0;i<images.length;i++){
pics.push(images[i].src)
}
console.log(pics)
addToGif(pics,gif);
fn()
}
var addToGif = function(images,gif, counter = 0) {
getPixels(images[counter], function(err, pixels) {
gif.addFrame(pixels.data);
gif.read();
if (counter === images.length - 1) {
gif.finish();
} else {
addToGif(images,gif, ++counter);
}
})
}
if i dont use GenerateThumbnail function it goes through 5 times as expected and everything works fine, but if i use it it goes through only once, and generated only 1 gif
Use var to declare for vars. Ie for(var i=0....
If you declare vars without var keyword, they are in the global scope. ..... and you are using another i var inside the function but now it is the same var from the outer for loop.

Resources