2019-03-19 14:53:09 +01:00
/ * *
* @ author j433866 [ j433866 @ gmail . com ]
* @ copyright Crown Copyright 2019
* @ license Apache - 2.0
* /
2019-07-09 13:23:59 +02:00
import Operation from "../Operation.mjs" ;
import OperationError from "../errors/OperationError.mjs" ;
import { isImage } from "../lib/FileType.mjs" ;
import { toBase64 } from "../lib/Base64.mjs" ;
import { isWorkerEnvironment } from "../Utils.mjs" ;
2023-02-27 18:55:52 +01:00
import jimp from "jimp" ;
2019-03-19 14:53:09 +01:00
/ * *
* Add Text To Image operation
* /
class AddTextToImage extends Operation {
/ * *
* AddTextToImage constructor
* /
constructor ( ) {
super ( ) ;
this . name = "Add Text To Image" ;
this . module = "Image" ;
2019-06-20 13:58:02 +02:00
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." ;
2019-03-19 14:53:09 +01:00
this . infoURL = "" ;
2019-04-01 11:54:46 +02:00
this . inputType = "ArrayBuffer" ;
this . outputType = "ArrayBuffer" ;
2019-03-19 14:53:09 +01:00
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
}
] ;
}
/ * *
2019-04-01 11:54:46 +02:00
* @ param { ArrayBuffer } input
2019-03-19 14:53:09 +01:00
* @ 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 ] ;
2019-09-04 14:54:59 +02:00
if ( ! isImage ( input ) ) {
2019-03-19 14:53:09 +01:00
throw new OperationError ( "Invalid file type." ) ;
}
let image ;
try {
2019-04-01 11:54:46 +02:00
image = await jimp . read ( input ) ;
2019-03-19 14:53:09 +01:00
} catch ( err ) {
throw new OperationError ( ` Error loading image. ( ${ err } ) ` ) ;
}
try {
2019-07-05 12:35:59 +02:00
if ( isWorkerEnvironment ( ) )
2019-03-19 14:53:09 +01:00
self . sendStatusMessage ( "Adding text to image..." ) ;
2019-04-01 10:00:41 +02:00
const fontsMap = { } ;
const fonts = [
import ( /* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/Roboto72White.fnt" ) ,
import ( /* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoBlack72White.fnt" ) ,
import ( /* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoMono72White.fnt" ) ,
import ( /* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoSlab72White.fnt" )
] ;
await Promise . all ( fonts )
. then ( fonts => {
fontsMap . Roboto = fonts [ 0 ] ;
fontsMap [ "Roboto Black" ] = fonts [ 1 ] ;
fontsMap [ "Roboto Mono" ] = fonts [ 2 ] ;
2019-04-01 11:54:46 +02:00
fontsMap [ "Roboto Slab" ] = fonts [ 3 ] ;
2019-04-01 10:00:41 +02:00
} ) ;
2019-03-19 14:53:09 +01:00
// Make Webpack load the png font images
2019-06-20 13:58:02 +02:00
await Promise . all ( [
2019-04-01 10:00:41 +02:00
import ( /* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/Roboto72White.png" ) ,
import ( /* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoSlab72White.png" ) ,
import ( /* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoMono72White.png" ) ,
import ( /* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoBlack72White.png" )
2019-06-20 13:58:02 +02:00
] ) ;
2019-03-19 14:53:09 +01:00
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 ;
}
}
}
} ) ;
2019-06-20 13:58:02 +02:00
// Create a temporary image to hold the rendered text
const textImage = new jimp ( jimp . measureText ( jimpFont , text ) , jimp . measureTextHeight ( jimpFont , text ) ) ;
textImage . print ( jimpFont , 0 , 0 , text ) ;
// Scale the rendered text image to the correct size
const scaleFactor = size / 72 ;
if ( size !== 1 ) {
2019-03-19 14:53:09 +01:00
// Use bicubic for decreasing size
2019-06-20 13:58:02 +02:00
if ( size > 1 ) {
textImage . scale ( scaleFactor , jimp . RESIZE _BICUBIC ) ;
2019-03-19 14:53:09 +01:00
} else {
2019-06-20 13:58:02 +02:00
textImage . scale ( scaleFactor , jimp . RESIZE _BILINEAR ) ;
2019-03-19 14:53:09 +01:00
}
}
// If using the alignment options, calculate the pixel values AFTER the image has been scaled
switch ( hAlign ) {
case "Left" :
xPos = 0 ;
break ;
case "Center" :
2019-06-20 13:58:02 +02:00
xPos = ( image . getWidth ( ) / 2 ) - ( textImage . getWidth ( ) / 2 ) ;
2019-03-19 14:53:09 +01:00
break ;
case "Right" :
2019-06-20 13:58:02 +02:00
xPos = image . getWidth ( ) - textImage . getWidth ( ) ;
2019-03-19 14:53:09 +01:00
break ;
}
switch ( vAlign ) {
case "Top" :
yPos = 0 ;
break ;
case "Middle" :
2019-06-20 13:58:02 +02:00
yPos = ( image . getHeight ( ) / 2 ) - ( textImage . getHeight ( ) / 2 ) ;
2019-03-19 14:53:09 +01:00
break ;
case "Bottom" :
2019-06-20 13:58:02 +02:00
yPos = image . getHeight ( ) - textImage . getHeight ( ) ;
2019-03-19 14:53:09 +01:00
break ;
}
2019-06-20 13:58:02 +02:00
// Blit the rendered text image onto the original source image
image . blit ( textImage , xPos , yPos ) ;
2019-03-19 14:53:09 +01:00
2019-03-20 12:20:34 +01:00
let imageBuffer ;
if ( image . getMIME ( ) === "image/gif" ) {
imageBuffer = await image . getBufferAsync ( jimp . MIME _PNG ) ;
} else {
imageBuffer = await image . getBufferAsync ( jimp . AUTO ) ;
}
2019-04-01 11:54:46 +02:00
return imageBuffer . buffer ;
2019-03-19 14:53:09 +01:00
} catch ( err ) {
throw new OperationError ( ` Error adding text to image. ( ${ err } ) ` ) ;
}
}
/ * *
* Displays the blurred image using HTML for web apps
*
2019-04-01 11:54:46 +02:00
* @ param { ArrayBuffer } data
2019-03-19 14:53:09 +01:00
* @ returns { html }
* /
present ( data ) {
2019-04-01 11:54:46 +02:00
if ( ! data . byteLength ) return "" ;
const dataArray = new Uint8Array ( data ) ;
2019-03-19 14:53:09 +01:00
2019-04-01 11:54:46 +02:00
const type = isImage ( dataArray ) ;
2019-03-19 14:53:09 +01:00
if ( ! type ) {
throw new OperationError ( "Invalid file type." ) ;
}
2019-04-01 11:54:46 +02:00
return ` <img src="data: ${ type } ;base64, ${ toBase64 ( dataArray ) } "> ` ;
2019-03-19 14:53:09 +01:00
}
}
export default AddTextToImage ;