Created
April 28, 2020 11:35
-
-
Save bennadel/868056f415c10514d211bfbb43de64b7 to your computer and use it in GitHub Desktop.
Creating A Partially-Transparent Overlay Using GraphicsMagick And Lucee CFML 5.2.9.31
This file contains hidden or 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
<cfscript> | |
startedAt = getTickCount(); | |
inputFilepath = expandPath( "../images/beach-small.jpg" ); | |
// In the first approach to drawing a partially-transparent image over another, we're | |
// going to create an intermediary image that represents the image overlay. This will | |
// the SCALED and PARTIALLY-TRANSPARENT image. | |
// -- | |
// NOTE: We're using the MIFF (Magick Image File Format) file-type to represent our | |
// intermediary image since it is both lossless and more efficient than a PNG file. | |
result = gm([ | |
"convert", | |
applyReader( inputFilepath ), | |
// Scale the image down to the dimensions that we want it to have in the final | |
// composite / overlay. | |
"-resize 300x224", | |
// Set the opacity of the image (0% is fully opaque). | |
// -- | |
// NOTE: We're using the "-matte" option to ensure that the image has an active | |
// opacity channel. If it doesn't the opacity operation will be ignored. | |
"-matte", | |
"-operator opacity assign 20%", | |
// Write to our temporary, lossless MIFF file. | |
expandPath( "./temp.miff" ) | |
]); | |
// Now that we have our temporary file that has been stored with both the desired | |
// dimensions and opacity, all we have to do is draw it (overlay it) on top of the | |
// destination canvas. | |
result = gm([ | |
"convert", | |
// For this demo, we're going to create a "blank" canvas that contains the | |
// traditional "checkerboard" background (so that we can see how the opacity of | |
// the overlay allows the background to show through). GraphicsMagick provides a | |
// built-in pattern image that we can use: CHECKERBOARD. | |
// -- | |
// NOTE: The subsequent "-fill" is adjusting the colors of the checkerboard. | |
"-size 500x300 tile:image:checkerboard", | |
"-fill ##fdfdfd -opaque ##999999", | |
"-fill ##cacaca -opaque ##666666", | |
// Overlay the temporary file. | |
// -- | |
// NOTE: By providing 0,0 as the overlay dimensions, we're telling GraphicsMagick | |
// to use the natural dimensions of the source image. | |
"-draw 'image over 100,37 0,0 #expandPath( "./temp.miff" )#'", | |
// And, finally, output our composite image! | |
expandPath( "./out.png" ) | |
]); | |
duration = ( getTickCount() - startedAt ); | |
// ------------------------------------------------------------------------------- // | |
// ------------------------------------------------------------------------------- // | |
/** | |
* I prefix the given filepath with an explicit reader. We want to be EXPLICIT about | |
* which input reader GraphicsMagick should use when reading in an image. If we leave | |
* it up to "automatic detection", a malicious actor could fake the file-type and | |
* potentially exploit a weakness in a given reader. As such, we want to align the | |
* reader with the articulated file-type. | |
* | |
* READ MORE: http://www.graphicsmagick.org/security.html | |
* | |
* @filepath I am the filepath being decorated. | |
*/ | |
public string function applyReader( required string filepath ) { | |
switch ( listLast( filepath, "." ).lcase() ) { | |
case "jpg": | |
case "jpeg": | |
var reader = "jpg"; | |
break; | |
case "gif": | |
var reader = "gif"; | |
break; | |
case "png": | |
var reader = "png"; | |
break; | |
default: | |
throw( type = "UnsupportedImageFileExtension" ); | |
break; | |
} | |
return( reader & ":""" & filepath & """" ); | |
} | |
/** | |
* I execute the given options against the GM (GraphicsMagick) command-line tool. If | |
* there is an error, the error is dumped-out and the processing is halted. If there | |
* is no error, the standard-output is returned. | |
* | |
* NOTE: Options are flattened using a space (" "). | |
* | |
* @options I am the collection of options to apply. | |
* @timeout I am the timeout to use during the execution. | |
*/ | |
public string function gm( | |
required array options, | |
numeric timeout = 5 | |
) { | |
execute | |
name = "gm" | |
arguments = options.toList( " " ) | |
variable = "local.successResult" | |
errorVariable = "local.errorResult" | |
timeout = timeout | |
; | |
// If the error variable has been populated, it means the CFExecute tag ran into | |
// an error - let's dump-it-out and halt processing. | |
if ( local.keyExists( "errorVariable" ) && errorVariable.len() ) { | |
dump( errorVariable ); | |
abort; | |
} | |
return( successResult ?: "" ); | |
} | |
</cfscript> | |
<cfoutput> | |
<p> | |
<img | |
src="./out.png" | |
width="500" | |
height="300" | |
/> | |
</p> | |
<p> | |
Duration: #numberFormat( duration )# ms | |
</p> | |
</cfoutput> |
This file contains hidden or 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
<cfscript> | |
startedAt = getTickCount(); | |
inputFilepath = expandPath( "../images/beach-small.jpg" ); | |
// In the second approach to drawing a partially-transparent image over another, | |
// we're going to use the COMPOSITE command instead of the CONVERT image. But, we're | |
// still going to create an intermediary image. This time, however, the intermediary | |
// image will be our background, not our overlay. | |
// -- | |
// NOTE: We're using the MIFF (Magick Image File Format) file-type to represent our | |
// intermediary image since it is both lossless and more efficient than a PNG file. | |
result = gm([ | |
"convert", | |
// For this demo, we're going to create a "blank" canvas that contains the | |
// traditional "checkerboard" background (so that we can see how the opacity of | |
// the overlay allows the background to show through). GraphicsMagick provides a | |
// built-in pattern image that we can use: CHECKERBOARD. | |
// -- | |
// NOTE: The subsequent "-fill" is adjusting the colors of the checkerboard. | |
"-size 500x300 tile:image:checkerboard", | |
"-fill ##fdfdfd -opaque ##999999", | |
"-fill ##cacaca -opaque ##666666", | |
// Write to our temporary, lossless MIFF file. | |
expandPath( "./out.miff" ) | |
]); | |
// Now that we have our temporary background image, we're going to COMPOSITE the two | |
// images together using "-dissolve". | |
result = gm([ | |
"composite", | |
// Scale and position the overlay image (our "change-image"). | |
"-geometry 300x224+100+37", | |
// Set the opacity of the overlay image (our "change-image"). | |
// -- | |
// NOTE: In the previous demo in which we "assigned" opacity, 0% represented a | |
// fully-opaque pixel. When using "dissolve", 100% represents a fully-opaque | |
// pixel in the resultant composite. | |
"-dissolve 80%", | |
// Overlay the "change-image" (the one that we want to be partially transparent) | |
// over our "base-image". | |
applyReader( inputFilepath ), | |
// Use our temporary MIFF file as the base image (our checkerboard background). | |
expandPath( "./out.miff" ), | |
// And, finally, output our composite image! | |
expandPath( "./out-2.png" ) | |
]); | |
duration = ( getTickCount() - startedAt ); | |
// ------------------------------------------------------------------------------- // | |
// ------------------------------------------------------------------------------- // | |
/** | |
* I prefix the given filepath with an explicit reader. We want to be EXPLICIT about | |
* which input reader GraphicsMagick should use when reading in an image. If we leave | |
* it up to "automatic detection", a malicious actor could fake the file-type and | |
* potentially exploit a weakness in a given reader. As such, we want to align the | |
* reader with the articulated file-type. | |
* | |
* READ MORE: http://www.graphicsmagick.org/security.html | |
* | |
* @filepath I am the filepath being decorated. | |
*/ | |
public string function applyReader( required string filepath ) { | |
switch ( listLast( filepath, "." ).lcase() ) { | |
case "jpg": | |
case "jpeg": | |
var reader = "jpg"; | |
break; | |
case "gif": | |
var reader = "gif"; | |
break; | |
case "png": | |
var reader = "png"; | |
break; | |
default: | |
throw( type = "UnsupportedImageFileExtension" ); | |
break; | |
} | |
return( reader & ":""" & filepath & """" ); | |
} | |
/** | |
* I execute the given options against the GM (GraphicsMagick) command-line tool. If | |
* there is an error, the error is dumped-out and the processing is halted. If there | |
* is no error, the standard-output is returned. | |
* | |
* NOTE: Options are flattened using a space (" "). | |
* | |
* @options I am the collection of options to apply. | |
* @timeout I am the timeout to use during the execution. | |
*/ | |
public string function gm( | |
required array options, | |
numeric timeout = 5 | |
) { | |
execute | |
name = "gm" | |
arguments = options.toList( " " ) | |
variable = "local.successResult" | |
errorVariable = "local.errorResult" | |
timeout = timeout | |
; | |
// If the error variable has been populated, it means the CFExecute tag ran into | |
// an error - let's dump-it-out and halt processing. | |
if ( local.keyExists( "errorVariable" ) && errorVariable.len() ) { | |
dump( errorVariable ); | |
abort; | |
} | |
return( successResult ?: "" ); | |
} | |
</cfscript> | |
<cfoutput> | |
<p> | |
<img | |
src="./out-2.png" | |
width="500" | |
height="300" | |
/> | |
</p> | |
<p> | |
Duration: #numberFormat( duration )# ms | |
</p> | |
</cfoutput> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment