3

I have a Pixbuf in some demo code I have and I currently rotate it clockwise or counter-clockwise depending on screen touches.

I do this using RotateSimple but that is limited to multiples of 90 degrees.

Is there a way within GDK to rotate images in Pixbuf buffers by 45 degrees (or less)?

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953

2 Answers2

3

I have written a general purpose pixbuf rotater function. Arguments:

  1. src -- pixbuf to rotate
  2. radian -- angle by which to rotate (expressed in radians)
  3. full_size -- This function works in two modes. It will either produce a new pixbuf which is just big enough to contain the fully rotated image (plus some extra triangles where alpha=0), or it will produce a pixbuf which is as big as the maximum inscribed (horizontal/vertical) rectangle within the rotated image. If full_size is true then the larger rectangle with (blank corners) is produced, If full_size is false then the smaller rectangle. When rotating by close to 45degrees the small rectangle may have a size 0 and a NULL pixbuf will be returned.

    #include <gtk/gtk.h>
    
    /* There are two reasonable sizes for a rotated image-- Either the minimum */
    /*  bounding box which contains all rotated pixels (and a bunch of white space)*/
    /*  or the maximum rectangle where all pixels come from the source image (but */
    /*  where we lose some of the corners) */
    /* The first is easy to calculate: The minimum bounding box will have the corners */
    /*  of the rotated image on its edges, this leaves us with four triangles in */
    /*  the corners of the bb. Two triangles have edges width*sin(theta), width*cos(theta) */
    /*  and two have edges height*sin(theta), height*cos(theta) */
    /*  so the new width height will be the sum of two adjacent triangle edges: */
    /*   width" = width*cos + height*sin */
    /*   height"= width*sin + height*cos */
    /* Now for the maximum inscribed rectangle we draw a similar picture (except */
    /*  the unknown rectangle is internal now) and get similar triangles. Here the*/
    /*  equations are: */
    /*   width = width'*cos + height'*sin */
    /*   height= width'*sin + height'*cos */
    /*  solving for height'... */
    /*   height' = (width-width'*cos)/sin */
    /*   height' = (height-width'*sin)/cos */
    /*   (width-width'*cos)/sin = (height-width'*sin)/cos */
    /*   width*cos - width'*cos^2 = height*sin - width'*sin^2 */
    /*   width' * (sin^2-cos^2) = height*sin-width*cos */
    /*   width' = (height*sin - width*cos)/(sin^2-cos^2) */
    /*   height'= (width*sin - height*cos)/(sin^2-cos^2) */
    /*  Note this produces garbage (0/0) when rotated by 45 degrees (135,225,...) */
    /*   A little experimentation shows that at 45 degrees the only thing with */
    /*   an internal rectangle is a square, all other aspect ratios have a height */
    /*   of 0. A square, however, has an internal square with sides  1/sqrt(2) of the original */
    /* When creating a full_size image (minimum bounding box) we should return */
    /*  an image with an alpha channel (whether the original had one or no). */
    /*  otherwise we should create an alpha channel only if the original had one */
    
    /* A pixel at (x,y) will be rotated to: */
    /*    ((x-width/2)*cos + (y-height/2)*sin + width'/2 ,                */
    /*    =(x-width/2)*sin + (y-height/2)*cos + height'/2 )                */
    /* A pixel at (x',y') will have come from: */
    /*    ((x'-width'/2)*cos - (y'-height'/2)*sin + width/2 ,                */
    /*     (x'-width'/2)*sin + (y'-height'/2)*cos + height/2 )                */
    static GdkPixbuf *gdk_pixbuf_rotate(GdkPixbuf *src,double radian,gboolean full_size) {
        double s = sin(radian), c = cos(radian);
        double as= s<0 ? -s : s, ac= c<0 ? -c : c;
        int width, height, nwidth, nheight;
        int hasalpha, nhasalpha;
        GdkPixbuf *ret;
        int nr,nc,r,col;
        double nmodr, nmodc;
        int alpha=0;
        guchar *pixels, *npixels, *pt, *npt;
        int rowstride, nrowstride, pixellen;
        if ( src==NULL )
            return( NULL );
        width     = gdk_pixbuf_get_width(src);
        height    = gdk_pixbuf_get_height(src);
        hasalpha  = gdk_pixbuf_get_has_alpha(src);
        rowstride = gdk_pixbuf_get_rowstride(src);
        pixels    = gdk_pixbuf_get_pixels(src);
        pixellen  = hasalpha ? 4 : 3;
        if ( full_size ) {
            nwidth = round( ac*width + as*height );
            nheight= round( as*width + ac*height );
            nhasalpha = TRUE;
        } else {
            double denom = as*as - ac*ac;
            if ( denom<.1e-7 && denom>-1.e-7 ) {
                if ( width!=height )
                    return( NULL );
                nwidth = nheight = round( width/sqrt(2.0) );
            } else {
                nwidth = round( (height*as - width*ac)/denom );
                nheight = round( (width*as - height*ac)/denom );
            }
            if ( nwidth<=0 || nheight<=0 )
                return( NULL );
            nhasalpha = hasalpha;
        }
        ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB,nhasalpha,8,nwidth,nheight);
        if ( ret==NULL )
            return( NULL );
        nrowstride = gdk_pixbuf_get_rowstride(ret);
        npixels    = gdk_pixbuf_get_pixels(ret);
        for ( nr=0; nr<nheight; ++nr ) {
            nmodr = nr-nheight/2.0;
            npt = npixels + nr*nrowstride;
            for ( nc=0; nc<nwidth; ++nc ) {
                nmodc = nc-nwidth/2.0;
                /* Where did this pixel come from? */
                r   = round( height/2 - nmodc*s + nmodr*c );
                col = round( width/2  + nmodc*c + nmodr*s );
                if ( r<0 || col<0 || r>=height || col>=width ) {
                    alpha = 0;
                    if ( r<0 ) r=0;
                    else if ( r>=height ) r = height-1;
                    if ( col<0 ) col = 0;
                    else if ( col>=width ) col = width-1;
                } else
                    alpha = 0xff;
                pt = pixels + r*rowstride + col*pixellen;
                *npt++ = *pt++;
                *npt++ = *pt++;
                *npt++ = *pt++;
                if ( hasalpha && alpha!=0 )
                    alpha = *pt;
                if ( nhasalpha )
                    *npt++ = alpha;        
            }
        }
        return( ret );
    }
1

No, not with RotateSimple (or gdk_pixbuf_rotate_simple() in the underlying libraries). As per the documentation, that is limited to rotating "by a multiple of 90 degrees".

However, one thing you can do is provide multiple images to make it appear as if you are rotating by a smaller value.

For your specific example of 45 degrees, only two images are needed. The first is the "upright" image for which you can use 90 degree rotations (i.e., using SimpleRotate) to get four of the eight required rotations, 0, 90, 180 and 270.

To get the other four possibilities, put the image into some image editing software and use that to rotate it 45 degrees, saving it as the "tilted" image.

That way, you can get all of the possibilities by using various rotations of the two images:

Desired rotation  Uses image  Actual rotation
----------------  ----------  ---------------
         0          upright            0
        45          tilted             0
        90          upright           90
       135          tilted            90
       180          upright          180
       225          tilted           180
       270          upright          270
       315          tilted           270

For more fine-grained rotations, you can do a similar thing, especially if the resolution of the rotation is some factor of 360. And, thanks to the forward-looking nature of the Babylonians (or Sumerians or someone, my history is a bit rusty), 360 has a rather large number of factors.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953