The first time I tried to rotate and scale a bitmap, I did the obvious choice: take each pixel from the source matrix, transform it with a rotation and scale, and write the value to the transformed location in the destination. The result looks like this:
bitmap = J( bmy = 200, bmx = 300, RGB Color( 1, 1, 1 ) );
scale = 3;
tranmat = (1 || 0 || 80) |/ (0 || 1 || 10) |/ (0 || 0 || 1);
scalmat = (scale || 0 || 0) |/ (0 || scale || 0) |/ (0 || 0 || 1);
angle = 33 * Pi() / 180;
sangle = Sin( angle );
cangle = Cos( angle );
rotamat = (cangle || -sangle || 0) |/ (sangle || cangle || 0) |/ (0 || 0 || 1);
forward = (tranmat * scalmat * rotamat)`;
testimage = Text Box( "matrix", <<setwrap( 1000 ), <<setfontsize( 18 ), <<Set Font Name( "Cambria" ) );
source = ((testimage << getpicture) << getpixels( "r" ))[1];
For( x = 1, x <= N Cols( source ), x += 1,
For( y = 1, y <= N Rows( source ), y += 1,
dest = (x || y || 1) * forward;
If( source[y, x] != 1,
bitmap[dest[2], dest[1]] = RGB Color( 1, 0, 0 )
);
)
);
oldimg = New Image( "rgb", {source, source, source} );
oldimg << scale( 1 );
newimg = New Image( bitmap );
newimg << scale( 1 );
New Window( "Wrong", oldimg, newimg );
Transformed text has holes
Looks terrible. The correct way is to use the inverse of the transform matrix and ask "For each pixel in the destination bitmap, what pixel should I use from the source?" But the answer will almost always fall between four pixels. A reasonable answer is to use a weighted average of the four pixels, and more complicated answers might look like bi-cubic interpolation. This code uses a weighted average:
bitmap = J( bmy = 300, bmx = 580, RGB Color( 1, 1, 1 ) );
drawtext = Function(
{x = 0, y = 0, txt = "hello", degAngle = 0, xjust = 0, yjust = 1, fontsize = 99, scale = .5, textcolor = RGB Color( 0, 0, 0 )},
{
t = Text Box( txt, <<setwrap( 1000 ), <<setfontsize( fontsize ), <<Set Font Name( "Cambria" ) )
, img = t << getpicture
, mat = (img << getpixels( "r" ))[1]
, xsize = N Cols( mat )
, ysize = N Rows( mat )
, justmat = (1 || 0 || (0 - xjust) * xsize) |/ (0 || 1 || (yjust - 1) * ysize) |/ (0 || 0 || 1)
, tranmat = (1 || 0 || x) |/ (0 || 1 || y) |/ (0 || 0 || 1)
, scalmat = (scale || 0 || 0) |/ (0 || scale || 0) |/ (0 || 0 || 1)
, angle = Pi() * degangle / 180, sangle = Sin( angle ), cangle = Cos( angle ), rotamat = (cangle || -sangle || 0) |/ (sangle || cangle || 0)
|/ (0 || 0 || 1)
, forward = (tranmat * scalmat * rotamat * justmat)`
, reverse = Inverse( forward )
, sourcerect =
(0 || 0 || 1) |/
(0 || ysize || 1) |/
(xsize || ysize || 1) |/
(xsize || 0 || 1)
, destrect = sourcerect * forward
, destminx = Max( 1, Min( bmx, Floor( Min( destrect[0, 1] ) ) ) )
, destmaxx = Max( 1, Min( bmx, Ceiling( Max( destrect[0, 1] ) ) ) )
, destminy = Max( 1, Min( bmy, Floor( Min( destrect[0, 2] ) ) ) )
, destmaxy = Max( 1, Min( bmy, Ceiling( Max( destrect[0, 2] ) ) ) )
, ri, gi, bi, ro, go, bo
},
{ri, gi, bi} = Color To RGB( textcolor );
For( destx = destminx, destx <= destmaxx, destx += 1,
For( desty = destminy, desty <= destmaxy, desty += 1,
dest = destx || desty || 1;
source = dest * reverse;
sourcelo = Floor( source );
ratio = source - sourcelo;
If( 1 <= sourcelo[2] < ysize & 1 <= sourcelo[1] < xsize,
q1 = mat[sourcelo[2], sourcelo[1]] * (1 - ratio[1]) + mat[sourcelo[2], sourcelo[1] + 1] * (ratio[1]);
q2 = mat[sourcelo[2] + 1, sourcelo[1]] * (1 - ratio[1]) + mat[sourcelo[2] + 1, sourcelo[1] + 1] * (ratio[1]);
q3 = q1 * (1 - ratio[2]) + q2 * (ratio[2]);
mq3 = 1 - q3;
{ro, go, bo} = Color To RGB( bitmap[desty, destx] );
bitmap[desty, destx] = RGB Color( mq3 * ri + q3 * ro, mq3 * gi + q3 * go, mq3 * bi + q3 * bo );
);
)
);
);
If( 1,
bitmap[0, 0] = RGB Color( 1, 1, 1 );
drawtext( 110, 40, "Matrix", 33, 0, 1, 18, 3 );
New Window( "Better", New Image( bitmap ) );
);
For( ipic = 1, ipic <= 2, ipic++,
bitmap[0, 0] = RGB Color( 1, 1, 1 );
drawtext( 110, 40, If( ipic == 1, "Matrix", "Matri " ), -2, 0, 1, 99, 0.84 );
drawtext( 70, 100, "InversE", 1, 0, 1, 99, 1.0 );
edges = New Image( bitmap );
{original} = edges << getpixels( "r" );
original = 1 - original;
original = 5 * original;
edges << filter( "edge" );
edges << filter( "gaussian blur", 4, 2 );
{gray} = edges << getpixels( "r" );
gray[1 :: 5, 0] = 0;
gray[N Rows( gray ) - 5 :: N Rows( gray ), 0] = 0;
compose = original || original || original;
width = N Cols( gray );
For( i = 1, i <= 64, i++,
For( dir = -1, dir <= 1, dir += 1,
pos = N Cols( gray ) + dir * i;
compose[0, pos :: pos + width - 1] += gray / i ^ .4;
)
);
compose /= Max( compose );
compose = compose[0, N Cols( original ) + 1 :: 2 * N Cols( original )];
edges = New Image( "rgb", {compose * 1, compose * .3, compose * .05} );
If( ipic == 1,
keepOn = edges
,
keepOff = edges
);
);
finalImg = New Image( keepOn );
finalImg << setFrameDuration( 20000 );
finalImg << addFrame;
finalImg << setPixels( keepOff << getPixels );
finalImg << setFrameDuration( 100 );
finalImg << addFrame;
finalImg << setPixels( keepOn << getPixels );
finalImg << setFrameDuration( 50 );
finalImg << addFrame;
finalImg << setPixels( keepOff << getPixels );
finalImg << setFrameDuration( 50 );
finalImg << saveImage( "$desktop/matrixBlinker.gif", "gif" );
Open( "$desktop/matrixBlinker.gif" );
Sampling the source image with linear interpolation
The last half of the code is not so much about matrix transforms as making an interesting picture. The horizontal streaking is created by blending a number of copies of the image together, each shifted a pixel, and keeping less of the more distant shifts.
Big, tilted, orange
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.