Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created April 28, 2020 11:35
Show Gist options
  • Save bennadel/868056f415c10514d211bfbb43de64b7 to your computer and use it in GitHub Desktop.
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
<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>
<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