-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add more image operations #515
Merged
Merged
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
21a8d03
Move parsing and generation of QR codes to lib folder.
j433866 11451ac
Add image format pattern.
j433866 3e428c0
Add min values to operation args
j433866 dfbc1be
Add sharpen image operation
j433866 e95b707
Add convert image format operation
j433866 3081952
Bring up to date with master
j433866 bb7487c
Change to use new FileType library
j433866 4fafa39
Fix magic library to better handle operation error
j433866 8e74acb
Add opaque background option
j433866 ce72acd
Add 'add text to image' operation.
j433866 2cd3e9c
Add new implementation of gaussian blur.
j433866 b312e17
Change title to title case
j433866 6a01e40
Fix bug where GIF input would error on output.
j433866 c2496fe
Change to use Promise.all
j433866 e44a22e
Change ops to use ArrayBuffer instead of byteArray
j433866 c97e77c
Merge with qr-improvements.
j433866 99bef09
Fix invalid file type error
j433866 0bcf57e
Improve printing text to improve output quality.
j433866 f473807
Bring up to date with master
j433866 be08a62
Add webpack config for font files
j433866 1135ca5
Remove duplicate function.
j433866 bed6629
Change jpeg test data to be a full image
j433866 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
/** | ||
* @author j433866 [[email protected]] | ||
* @copyright Crown Copyright 2019 | ||
* @license Apache-2.0 | ||
*/ | ||
|
||
import Operation from "../Operation"; | ||
import OperationError from "../errors/OperationError"; | ||
import { isImage } from "../lib/FileType"; | ||
import { toBase64 } from "../lib/Base64"; | ||
import jimp from "jimp"; | ||
|
||
/** | ||
* Add Text To Image operation | ||
*/ | ||
class AddTextToImage extends Operation { | ||
|
||
/** | ||
* AddTextToImage constructor | ||
*/ | ||
constructor() { | ||
super(); | ||
|
||
this.name = "Add Text To Image"; | ||
this.module = "Image"; | ||
this.description = "Adds text onto an image.<br><br>Text can be horizontally or vertically aligned, or the position can be manually specified.<br>Variants of the Roboto font face are available in any size or colour.<br><br>Note: This may cause a degradation in image quality, especially when using font sizes larger than 72."; | ||
this.infoURL = ""; | ||
this.inputType = "byteArray"; | ||
this.outputType = "byteArray"; | ||
this.presentType = "html"; | ||
this.args = [ | ||
{ | ||
name: "Text", | ||
type: "string", | ||
value: "" | ||
}, | ||
{ | ||
name: "Horizontal align", | ||
type: "option", | ||
value: ["None", "Left", "Center", "Right"] | ||
}, | ||
{ | ||
name: "Vertical align", | ||
type: "option", | ||
value: ["None", "Top", "Middle", "Bottom"] | ||
}, | ||
{ | ||
name: "X position", | ||
type: "number", | ||
value: 0 | ||
}, | ||
{ | ||
name: "Y position", | ||
type: "number", | ||
value: 0 | ||
}, | ||
{ | ||
name: "Size", | ||
type: "number", | ||
value: 32, | ||
min: 8 | ||
}, | ||
{ | ||
name: "Font face", | ||
type: "option", | ||
value: [ | ||
"Roboto", | ||
"Roboto Black", | ||
"Roboto Mono", | ||
"Roboto Slab" | ||
] | ||
}, | ||
{ | ||
name: "Red", | ||
type: "number", | ||
value: 255, | ||
min: 0, | ||
max: 255 | ||
}, | ||
{ | ||
name: "Green", | ||
type: "number", | ||
value: 255, | ||
min: 0, | ||
max: 255 | ||
}, | ||
{ | ||
name: "Blue", | ||
type: "number", | ||
value: 255, | ||
min: 0, | ||
max: 255 | ||
}, | ||
{ | ||
name: "Alpha", | ||
type: "number", | ||
value: 255, | ||
min: 0, | ||
max: 255 | ||
} | ||
]; | ||
} | ||
|
||
/** | ||
* @param {byteArray} input | ||
* @param {Object[]} args | ||
* @returns {byteArray} | ||
*/ | ||
async run(input, args) { | ||
const text = args[0], | ||
hAlign = args[1], | ||
vAlign = args[2], | ||
size = args[5], | ||
fontFace = args[6], | ||
red = args[7], | ||
green = args[8], | ||
blue = args[9], | ||
alpha = args[10]; | ||
|
||
let xPos = args[3], | ||
yPos = args[4]; | ||
|
||
if (!isImage(input)) { | ||
throw new OperationError("Invalid file type."); | ||
} | ||
|
||
let image; | ||
try { | ||
image = await jimp.read(Buffer.from(input)); | ||
} catch (err) { | ||
throw new OperationError(`Error loading image. (${err})`); | ||
} | ||
try { | ||
if (ENVIRONMENT_IS_WORKER()) | ||
self.sendStatusMessage("Adding text to image..."); | ||
|
||
const fontsMap = { | ||
"Roboto": await import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/Roboto72White.fnt"), | ||
"Roboto Black": await import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoBlack72White.fnt"), | ||
"Roboto Mono": await import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoMono72White.fnt"), | ||
"Roboto Slab": await import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoSlab72White.fnt") | ||
}; | ||
|
||
// Make Webpack load the png font images | ||
await import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/Roboto72White.png"); | ||
await import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoSlab72White.png"); | ||
await import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoMono72White.png"); | ||
await import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoBlack72White.png"); | ||
|
||
const font = fontsMap[fontFace]; | ||
|
||
// LoadFont needs an absolute url, so append the font name to self.docURL | ||
const jimpFont = await jimp.loadFont(self.docURL + "/" + font.default); | ||
|
||
jimpFont.pages.forEach(function(page) { | ||
if (page.bitmap) { | ||
// Adjust the RGB values of the image pages to change the font colour. | ||
const pageWidth = page.bitmap.width; | ||
const pageHeight = page.bitmap.height; | ||
for (let ix = 0; ix < pageWidth; ix++) { | ||
for (let iy = 0; iy < pageHeight; iy++) { | ||
const idx = (iy * pageWidth + ix) << 2; | ||
|
||
const newRed = page.bitmap.data[idx] - (255 - red); | ||
const newGreen = page.bitmap.data[idx + 1] - (255 - green); | ||
const newBlue = page.bitmap.data[idx + 2] - (255 - blue); | ||
const newAlpha = page.bitmap.data[idx + 3] - (255 - alpha); | ||
|
||
// Make sure the bitmap values don't go below 0 as that makes jimp very unhappy | ||
page.bitmap.data[idx] = (newRed > 0) ? newRed : 0; | ||
page.bitmap.data[idx + 1] = (newGreen > 0) ? newGreen : 0; | ||
page.bitmap.data[idx + 2] = (newBlue > 0) ? newBlue : 0; | ||
page.bitmap.data[idx + 3] = (newAlpha > 0) ? newAlpha : 0; | ||
} | ||
} | ||
} | ||
}); | ||
|
||
// Scale the image to a factor of 72, so we can print the text at any size | ||
const scaleFactor = 72 / size; | ||
if (size !== 72) { | ||
// Use bicubic for decreasing size | ||
if (size > 72) { | ||
image.scale(scaleFactor, jimp.RESIZE_BICUBIC); | ||
} else { | ||
image.scale(scaleFactor, jimp.RESIZE_BILINEAR); | ||
} | ||
} | ||
|
||
// If using the alignment options, calculate the pixel values AFTER the image has been scaled | ||
switch (hAlign) { | ||
case "Left": | ||
xPos = 0; | ||
break; | ||
case "Center": | ||
xPos = (image.getWidth() / 2) - (jimp.measureText(jimpFont, text) / 2); | ||
break; | ||
case "Right": | ||
xPos = image.getWidth() - jimp.measureText(jimpFont, text); | ||
break; | ||
default: | ||
// Adjust x position for the scaled image | ||
xPos = xPos * scaleFactor; | ||
} | ||
|
||
switch (vAlign) { | ||
case "Top": | ||
yPos = 0; | ||
break; | ||
case "Middle": | ||
yPos = (image.getHeight() / 2) - (jimp.measureTextHeight(jimpFont, text) / 2); | ||
break; | ||
case "Bottom": | ||
yPos = image.getHeight() - jimp.measureTextHeight(jimpFont, text); | ||
break; | ||
default: | ||
// Adjust y position for the scaled image | ||
yPos = yPos * scaleFactor; | ||
} | ||
|
||
image.print(jimpFont, xPos, yPos, text); | ||
|
||
if (size !== 72) { | ||
if (size > 72) { | ||
image.scale(1 / scaleFactor, jimp.RESIZE_BILINEAR); | ||
} else { | ||
image.scale(1 / scaleFactor, jimp.RESIZE_BICUBIC); | ||
} | ||
} | ||
|
||
const imageBuffer = await image.getBufferAsync(jimp.AUTO); | ||
return [...imageBuffer]; | ||
} catch (err) { | ||
throw new OperationError(`Error adding text to image. (${err})`); | ||
} | ||
} | ||
|
||
/** | ||
* Displays the blurred image using HTML for web apps | ||
* | ||
* @param {byteArray} data | ||
* @returns {html} | ||
*/ | ||
present(data) { | ||
if (!data.length) return ""; | ||
|
||
const type = isImage(data); | ||
if (!type) { | ||
throw new OperationError("Invalid file type."); | ||
} | ||
|
||
return `<img src="data:${type};base64,${toBase64(data)}">`; | ||
} | ||
|
||
} | ||
|
||
export default AddTextToImage; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use Promise.all here so we're not awaiting for each of these to load individually? Not sure whether webpack will be friends with that though...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would something like this be better? Webpack seems to be happy with it.