asmerge(3x)

πŸ’… asmerge(3x) β€” AfterStep X11 window manager πŸ’…

okay so bestie, welcome to asmerge, straight from the AfterStep X11 window manager manual, section 3x. πŸŽ€ it's a tutorial page from libAfterImage/tutorials/ASMerge and it's all about blending a whole stack of images together with different algorithms. it's giving ✨image-composition main character energy✨ and honestly? obsessed.

this page is long so it comes in parts, and this is part 1 β€” buckle in, we are not skimming. πŸ’–


🌈 NAME 🌈

asmerge β€” demonstrates blending of multiple image using different algorithms libAfterImage/tutorials/ASMerge

πŸ¦‹ NAMEASMerge πŸ¦‹

Scaling and blending of arbitrary number of images using libAfterImage.

🌸 SYNOPSIS 🌸

Scaling and blending of arbitrary number of images using libAfterImage.

✨ DESCRIPTION ✨

So here's the vibe: we're gonna take the command line arguments and read them as a sequence of image filenames, geometries and blending types. 😌 then we load all those images up, scale the very first one to the size we asked for, and blend all the rest of them onto specific locations of that first image. then we pop the whole result into a simple little window, and we just… wait. patiently. until the user closes our window. πŸ’… truly the group chat sitting around waiting for the one friend who's still typing.

The new steps this tutorial walks through are:

  1. Layers.
  2. Merging methods.
  3. Layer parameters.
  4. Actual blending of the set of images.

no cap, that's the whole plot. 🫢

πŸ’œ SEE ALSO πŸ’œ

  • Tutorial 1: ASView β€” explanation of basic steps needed to use libAfterImage and some other simple things.
  • Tutorial 2: ASScale β€” image scaling basics.
  • Tutorial 3: ASTile β€” image tiling and tinting

πŸ”₯ SOURCE πŸ”₯

#include "../afterbase.h" #include "../afterimage.h" #include
"common.h"

char *burning_rose[] = {
    "asmerge",
    "rose512.jpg",
    "add",
    "back.xpm:512x386",
    "hue",
    "fore.xpm:512x386" };

void usage() {
    printf( "Usage: asmerge [-h]|[image op1 image1 [op2 image2
[...]]]\n");
    printf( "Where: image  - is background image filename\n");
    printf( "       image1 - is first overlay's filename\n");
    printf( "       op1,op2,... - overlay operation."
            " Supported operations are :\n");
    list_scanline_merging( stdout,
            "         %-15.15s- %s\n"); }

int main(int argc, char* argv[]) {
    Display *dpy = NULL;
    ASVisual *asv ;
    int screen = 0, depth = 0;
    int to_width = 1, to_height = 1;
    ASImageLayer *layers ;
    int layers_num = 0, i;
    ASImage *merged_im ;

    /* see ASView.1 : */
    set_application_name( argv[0] ); #if (HAVE_AFTERBASE_FLAG==1)
    set_output_threshold(OUTPUT_LEVEL_DEBUG); #endif
    if( argc == 2 && strncmp(argv[1],"-h", 2) == 0 )
    {
        usage();
        return 0;
    }
    if( argc <= 3 )
    {
        show_error( "not enough arguments, please see usage:%s", "
");
        usage() ;
        printf( "Using the default, \"The Burning Rose\",
composition :\n");
        printf( "\n\trose512.jpg add back.xpm:512x386 hue "
                "fore.xpm:512x386\n");
        argv = &(burning_rose[0]) ;
        argc = 6;
    }

#ifndef X_DISPLAY_MISSING
    dpy = XOpenDisplay(NULL);
    _XA_WM_DELETE_WINDOW = XInternAtom( dpy, "WM_DELETE_WINDOW",
False);
    screen = DefaultScreen(dpy);
    depth = DefaultDepth( dpy, screen ); #endif
    /* see ASView.3 : */
    asv = create_asvisual( dpy, screen, depth, NULL );

    /* see ASMerge.1 : */
    layers = safecalloc( argc/2, sizeof(ASImageLayer) );

    for( i = 1 ; i < argc ; i++ )
    {
        int x = 0, y = 0;
        unsigned int width, height ;
        int geom_flags = 0 ;
        char *separator;
        char *filename ;
        /* see ASMerge.2 */
        if( i > 1 )
        {
            /* see blend_scanlines_name2func() : */
            if((layers[layers_num].merge_scanlines =
                 blend_scanlines_name2func( argv[i] )) == NULL )
                continue ;
            if( ++i >= argc )
                break;
        }
        if( (separator = strchr( argv[i], ':' )) != NULL )
        {   /* see ASTile.1 : */
            geom_flags = XParseGeometry( separator+1,
                                         &x, &y, &width, &height);
            filename = mystrndup( argv[i], separator-argv[i] );
        }else
            filename = argv[i] ;
        layers[layers_num].im = file2ASImage( filename, 0xFFFFFFFF,
                                              SCREEN_GAMMA, 100,
getenv("IMAGE_PATH"), NULL );
        if( filename != argv[i] )
            free( filename );
        if( layers[layers_num].im != NULL )
        {
            if( !get_flags(geom_flags, WidthValue) )
                width = layers[layers_num].im->width  ;
            if( !get_flags(geom_flags, HeightValue) )
                height = layers[layers_num].im->height ;
            /* see ASMerge.3 : */
            if( layers[layers_num].merge_scanlines == NULL )
                layers[layers_num].merge_scanlines =
                    alphablend_scanlines ;
            layers[layers_num].clip_width = width ;
            layers[layers_num].clip_height = height ;
            if( layers_num > 0 )
            {
                layers[layers_num].dst_x = x ;
                layers[layers_num].dst_y = y ;
            }else
            {
                to_width = width ;
                to_height = height ;
                if( width != layers[layers_num].im->width ||
                    height != layers[layers_num].im->height )
                {
                    ASImage *scaled_bottom ;
                    /* see ASScale.2 : */
                    scaled_bottom = scale_asimage( asv,
                                                   layers[layers_num].im,
                                                   width, height,
                                                   False, 100,
                                                ASIMAGE_QUALITY_DEFAULT
);
                    destroy_asimage( &(layers[layers_num].im) );
                    layers[layers_num].im = scaled_bottom ;
                }
            }
            ++layers_num ;
        }
    }

    if( layers_num <= 0 )
    {
        show_error( "there is no images to merge. Aborting");
        return 2;
    }

    /* see ASMerge.4 */
    merged_im = merge_layers( asv, layers, layers_num,
                              to_width, to_height, #ifndef
X_DISPLAY_MISSING
                              ASA_XImage, #else
                              ASA_ASImage, #endif
                              0, ASIMAGE_QUALITY_DEFAULT );
    while( --layers_num >= 0 )
        destroy_asimage( &(layers[layers_num].im) );
    free( layers );

    if( merged_im )
    { #ifndef X_DISPLAY_MISSING
    /* see ASView.4 : */
        Window w = create_top_level_window( asv,
DefaultRootWindow(dpy),
                                            32, 32,
                                            to_width, to_height,
                                            1, 0, NULL,
                                            "ASMerge", NULL );
        if( w != None )
        {
            Pixmap p ;

            XMapRaised   (dpy, w);
            /* see ASView.5 : */
            p = asimage2pixmap( asv, DefaultRootWindow(dpy),
merged_im,
                                NULL, True );

            destroy_asimage( &merged_im );
            /* see common.c: set_window_background_and_free() : */
            p = set_window_background_and_free( w, p );
            /* see common.c: wait_closedown() : */
        }
        wait_closedown(w);
        dpy = NULL; #else
        /* writing result into the file */
        ASImage2file( merged_im, NULL, "asmerge.jpg", ASIT_Jpeg,
NULL );
        destroy_asimage( &merged_im ); #endif
    } #ifdef DEBUG_ALLOCS
    build_xpm_colormap(NULL);
    print_unfreed_mem(); #endif
    return 0 ; }

πŸŽ€ libAfterImage/tutorials/ASMerge.1 [4.1] πŸŽ€

⭐ SYNOPSIS ⭐

Step 1. Layers.

πŸ’« DESCRIPTION πŸ’«

So libAfterImage does its blending/merging of different images using arrays of ASImageLayer structures, and the first element in that array is the bottommost image. πŸ‘‘ everyone else stacks on top.

Each one of those structures tells you:

  • the exact position on the resulting image where that overlay should be blended to,
  • the tint of the overlay,
  • the size, to which the overlay should be tiled,
  • the overlay's origin,
  • and the overlay's background color.

and the slay part? an arbitrary number of layers can be merged within a single run. πŸ”₯ no cap.

So accordingly, all you need to merge a bunch of images is to create an array of ASImageLayer structures and fill it up appropriately. that's it, that's the step. πŸ’…

πŸ¦‹ EXAMPLE πŸ¦‹

layers = safecalloc( argc/2, sizeof(ASImageLayer) );

🌸 libAfterImage/tutorials/ASMerge.2 [4.2] 🌸

⭐ SYNOPSIS ⭐

Step 2. Merging methods.

πŸ’« DESCRIPTION πŸ’«

Every single layer can be merged in using its own method β€” there are about 15 different methods implemented in libAfterImage, and your app can implement other methods of its own too. πŸ’– so custom, so iconic.

to specify a method, all you gotta do is set the merge_scanlines member of ASImageLayer to a pointer to the function that implements that specific method.

and libAfterImage provides a facility to parse method name strings into actual function pointers β€” which you can use to simplify scripting, etc. 🫢 convenient much?

πŸ¦‹ EXAMPLE πŸ¦‹

if((layers[layers_num].merge_scanlines =
    blend_scanlines_name2func( argv[i] )) == NULL )
    continue ;
NOTE β€” all layers MUST have a valid merge_scanlines pointer, even the bottommost layer, even though it lowkey has nothing to be merged with. 🫠 if merge_scanlines is set to NULL, that layer will be ignored. which, honestly, means you can use it to turn particular layers on and off. πŸ’…

πŸ’œ libAfterImage/tutorials/ASMerge.3 [4.3] πŸ’œ

⭐ SYNOPSIS ⭐

Step 3. Layer parameters.

πŸ’« DESCRIPTION πŸ’«

Several ASImageLayer members are mandatory and cannot be set to 0, bestie. those are:

  • im β€” the image to be merged;
  • clip_width, clip_height β€” these will be used to tile the image;
  • merge_scanlines β€” must be set to a pointer to the function implementing the merging method.

if any of these is set to 0, then the layer will be ignored. 🚫

The rest of the parameters are optional. but note that the tint parameter will tint the overlay's RGB components and the alpha component β€” and as a result, it could be used to make opaque images semitransparent. πŸ‘€βœ¨

✨ EXAMPLE ✨

okay so real talk, here's the actual prepping-a-layer moment in code, no edits, exactly as it lives:

if( layers[layers_num].merge_scanlines == NULL )
    layers[layers_num].merge_scanlines =
        alphablend_scanlines ;
layers[layers_num].clip_width = width ;
layers[layers_num].clip_height = height ;
if( layers_num > 0 )
{
    layers[layers_num].dst_x = x ;
    layers[layers_num].dst_y = y ;
}

bestie the way this little chunk just… sets merge_scanlines to alphablend_scanlines if there isn't one already? iconic. it stamps the clip_width and clip_height, and if you're not on layer zero it also drops in dst_x and dst_y. main character behaviour fr fr πŸŽ€

and where does this gorgeous specimen live? libAfterImage/tutorials/ASMerge.4 [4.4] πŸ’…


🌈 SYNOPSIS 🌈

Step 4. the actual blending. the moment we've all been waiting for. the layers get fused together, for real this time ✨

πŸ’– DESCRIPTION πŸ’–

so once your whole lil set of layers is prepped and ready and lookin cute β€” you hand it off to the merge_layers() function. she takes that stack and creates a brand-new ASImage of the size you specified, and then blends every single layer together to fill that image up. that's the whole vibe. that's the point. periodt πŸ‘‘

πŸ¦‹ EXAMPLE πŸ¦‹

here's the glow-up in motion:

merged_im = merge_layers( asv, layers, layers_num,
                          to_width, to_height,
                          ASA_ASImage, 0,
                          ASIMAGE_QUALITY_DEFAULT );
while( --layers_num >= 0 )
    destroy_asimage( &(layers[layers_num].im) );
free( layers );

look at her go 😭 blending everything, then walking backwards through the layer count to destroy_asimage( &(layers[layers_num].im) ) on each one, then free( layers ) at the end. clean. tidy. no leaks. we love to see it 🫢

🌸 NOTES 🌸

so once the layers are blended β€” plot twist β€” we do not need that ASImageLayer array anymore. so obviously we clean it up. the order matters though, bestie: you destroy the overlay AsImages first, and then you free the array itself. do it in that order. don't be messy about it πŸ’…

⭐ SEE ALSO ⭐

  • merge_asimage().

and that's a wrap on this whole saga, bestie πŸ’œ this page came to you from the 3rd Berkeley Distribution, shipping with AfterStep v.2.2.12, dated back in the day as asmerge(3x). go blend something gorgeous, you absolute legend πŸ‘‘βœ¨πŸŽ€

πŸ“¦ where this page comes from