Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Ways to create streched canvas view

Ways to create streched canvas view

Problem

Here is what I want to achive,

Pure image,

enter image description here

Created image,

enter image description here

I have seen lots of apps doing this (http://fineartamerica.com/ is one of them), but I was wondering how it can be done?, what kind of method, algorithm or programming language should I use?

I tried that with css3 bu couldn't get a tangible result, and it won't be a cross browser solution,

-webkit-transform: perspective( 800px ) rotatey( 78deg );

I'm looking for a server side solution.

Problem courtesy of: ocanal

Solution

I wrote an app that does what you want but it turns out that there are some problems with the example image you gave. Using your example as a guide, the output of this app looks very close as you can see.

Ex.1) The logic behind formatting the right-side edge is to take the right 10% of the original image and then reflect it around the Y-axis, squash it to 5%, and then warp it. In some cases it looks fine.

Img1

The problem happens when you use an image like this one:

enter image description here

The foot gets pulled into the reflected image. A similar problem happens when you gallery wrap a real pre-painted canvas. You either lose the outer border of the painting or you get white sides.

So, after looking at the http://fineartamerica.com link you mentioned, I also included a modified version of what they do there. The difference is that there is a white matte around the image along with the drop shadow like the image below. That way, there are 2 code bases to experiment with.

enter image description here

Examples:

enter image description hereenter image description hereenter image description here

Ok, so this is quite a bit of code. If you are going to use it server-side in asp.net, wrap it up in it's own dll and reference it. There is some unsafe code and asp.net might bark at you otherwise.

About the code

  • I used a few different pieces that I found on the web and used them to create a usable class. Most importantly, the source from http://www.vcskicks.com/image-distortion.php provided great info on applying quadrilateral distortions to images using GDI+.
  • There are magic numbers littered throughout the code because I was mostly interested in recreating your requested result. I just didn't have time to make a totally configurable control.
  • The gallery wrap version is MUCH slower that the matted one.
  • The GetCanvasedImage and GetMattedImage functions can be copied or modified to create your own custom image formats.
  • The SHADOW_DEPTH constant is hard-coded and must be manually adjusted when switching between wrapped or matted output, and/or changing the MAX_LENGTH value. Obviously these could be changed into variables.
  • The MAX_LENGTH value is used to constrain either the width or the height to a maximum value. It just applies it to the longest edge.
  • Just copy both classes into a project, a separate namespace, or a separate assembly. Then, pass in a path to an image and get a formatted image as a return value.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1 {
    public class ImageFormatter {
        public ImageFormatter() {
        }

        private const int MAX_LENGTH = 500;     // Constrain size to the longest edge
        private const int SHADOW_DEPTH = 30;    // This number has to be adjusted to taste 
                                                // for each change in MAX_LENGTH
        private const double PIOver2 = Math.PI / 2.0;

        /// 
        /// Creates a 3D representation of a 2D image, as if stretched around stretcher bars.
        /// 
        public Image GetCanvasedImage(string srcImgPath) {

            Image src = SizeSourceImage(Bitmap.FromFile(srcImgPath));
            Bitmap output = new Bitmap(src, src.Width + (int)(src.Width * .125f), src.Height + (int)(src.Height * .125f));

            using (Bitmap side = new Bitmap((int)(src.Width * .10f), src.Height))
            using (GraphicsPath outline = new GraphicsPath())
            using (Graphics destGraphics = Graphics.FromImage(output)) {

                // Set graphics options
                destGraphics.Clear(Color.White);
                destGraphics.InterpolationMode = InterpolationMode.Bicubic;
                destGraphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
                destGraphics.SmoothingMode = SmoothingMode.AntiAlias;

                Rectangle shadowRect = new Rectangle(
                    (int)(src.Width * .02),
                    (int)(src.Height * .03),
                    src.Width + (int)(src.Width * .07),
                    src.Height + (int)(src.Height * .03)
                );

                // Counter-clockwise points of front rectangle
                Point[] destPoints = new Point[] { 
                        new Point(0, (int)(src.Height * .02f)), 
                        new Point(src.Width, 0), 
                        new Point(src.Width, src.Height),
                        new Point(0, src.Height - (int)(src.Height * .02f))
                    };

                outline.AddPolygon(destPoints);

                // Draw the drop shadow first
                DrawRectangleDropShadow(
                    destGraphics,
                    shadowRect,
                    Color.FromArgb(25, 25, 25),
                    SHADOW_DEPTH,
                    128
                );

                // Draw front rectangle warped
                destGraphics.DrawImage(ImageFormatter.Distort(
                    (Bitmap)src,
                    destPoints[0],
                    destPoints[1],
                    destPoints[3],
                    destPoints[2],
                    3
                ), 0, 0);

                // Create the inverted right-side graphic
                src.RotateFlip(RotateFlipType.Rotate180FlipY);

                using (Graphics sideGraphics = Graphics.FromImage(side)) {
                    sideGraphics.DrawImage(
                        src, 
                        new Rectangle(0, 0, side.Width, side.Height),
                        new Rectangle(new Point(0, 0), new Size(side.Width, side.Height)),
                        GraphicsUnit.Pixel);
                }

                destPoints = new Point[] { 
                    new Point(src.Width, 0), 
                    new Point(src.Width + (int)(side.Width * .5f), (int)(side.Height * .03f)), 
                    new Point(src.Width + (int)(side.Width * .5f), src.Height - (int)(side.Height * .03f)), 
                    new Point(src.Width, src.Height)
                };

                // Draw side rectangle warped
                destGraphics.DrawImage(ImageFormatter.Distort(
                    (Bitmap)side,
                    destPoints[0],
                    destPoints[1],
                    destPoints[3],
                    destPoints[2],
                    3
                ), 0, 0);

                outline.AddPolygon(destPoints);

                // Draw outline
                GraphicsPath p = new GraphicsPath(FillMode.Alternate);
                destGraphics.DrawPath(Pens.Black, outline);
            }

            return output;
        }

        /// 
        /// Creates a matted representation of an image with accompanying drop shadow
        /// 
        public Image GetMattedImage(string srcImgPath) {

            Image src = SizeSourceImage(Bitmap.FromFile(srcImgPath));
            int borderWidth = (int)(src.Width * .05f);

            Rectangle imageRect = new Rectangle(
                0, 
                0, 
                src.Width + borderWidth * 2, 
                src.Height + borderWidth * 2
            );

            Rectangle outputRect = new Rectangle(
                0,
                0,
                src.Width + borderWidth * 2 + (int)(src.Height * .10f), 
                src.Height + borderWidth * 2 + (int)(src.Height * .10f)
            );

            Rectangle shadowRect = imageRect;
            shadowRect.Inflate(8, 8);
            shadowRect.Offset(5, 5);

            Bitmap output = new Bitmap(outputRect.Width, outputRect.Height);

            using (GraphicsPath outline = new GraphicsPath())
            using (Graphics destGraphics = Graphics.FromImage(output)) {

                // Set graphics options
                destGraphics.Clear(Color.White);
                destGraphics.InterpolationMode = InterpolationMode.Bicubic;
                destGraphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
                destGraphics.SmoothingMode = SmoothingMode.AntiAlias;

                // Draw shadow
                DrawRectangleDropShadow(
                    destGraphics,
                    shadowRect,
                    Color.FromArgb(25, 25, 25),
                    SHADOW_DEPTH,
                    128
                );

                // Draw image
                destGraphics.FillRectangle(Brushes.White, imageRect);
                destGraphics.DrawImage(src, borderWidth, borderWidth);
                destGraphics.DrawRectangle(Pens.Black, imageRect);

                // Draw outline
                //destGraphics.DrawPath(Pens.Black, outline);
            }

            return output;
        }

        private Image SizeSourceImage(Image src) {
            int newWidth = 0;
            int newHeight = 0;

            if (src.Width >= src.Height) {
                if (src.Width  sourceWidth) && (top  sourceHeight)) 
                        continue;

                    //weights
                    double xFrac = xP0 - (double)left;
                    double xFracRec = 1.0 - xFrac;
                    double yFrac = yP0 - (double)top;
                    double yFracRec = 1.0 - yFrac;

                    //get source pixel colors, or white if out of range (to interpolate into the background color)
                    int x0;
                    int y0;
                    Color color;

                    for (int sx = 0; sx  0 && y0 > 0 &&
                                x0  to.X)
                    return Math.PI;
                else
                    return 0;
            }
            else if (to.X == from.X) {
                if (to.Y  0)
                        return m + PIOver2;
                    else
                        return m - Math.PI;
                else
                    return m;
            }
        }

        private static void DrawRectangleDropShadow(Graphics tg, Rectangle rc, Color shadowColor, int depth, int maxOpacity) {
            //calculate the opacities
            Color darkShadow = Color.FromArgb(maxOpacity, shadowColor);
            Color lightShadow = Color.FromArgb(0, shadowColor);

            //Create a shape for the path gradient brush
            using (GraphicsPath gp = new GraphicsPath()) 
            using (Bitmap patternbm = new Bitmap(2 * depth, 2 * depth))
            using (Graphics g = Graphics.FromImage(patternbm)) {
                gp.AddEllipse(0, 0, 2 * depth, 2 * depth);

                //Create the brush that will draw a softshadow circle
                using (PathGradientBrush pgb = new PathGradientBrush(gp)) {
                    pgb.CenterColor = darkShadow;
                    pgb.SurroundColors = new Color[] { lightShadow };
                    g.FillEllipse(pgb, 0, 0, 2 * depth, 2 * depth);


                    SolidBrush sb = new SolidBrush(Color.FromArgb(maxOpacity, shadowColor));
                    tg.FillRectangle(sb, rc.Left + depth, rc.Top + depth, rc.Width - (2 * depth), rc.Height - (2 * depth));
                    sb.Dispose();

                    //top left corner
                    tg.DrawImage(patternbm, new Rectangle(rc.Left, rc.Top, depth, depth), 0, 0, depth, depth, GraphicsUnit.Pixel);
                    //top side
                    tg.DrawImage(patternbm, new Rectangle(rc.Left + depth, rc.Top, rc.Width - (2 * depth), depth), depth, 0, 1, depth, GraphicsUnit.Pixel);
                    //top right corner
                    tg.DrawImage(patternbm, new Rectangle(rc.Right - depth, rc.Top, depth, depth), depth, 0, depth, depth, GraphicsUnit.Pixel);
                    //right side
                    tg.DrawImage(patternbm, new Rectangle(rc.Right - depth, rc.Top + depth, depth, rc.Height - (2 * depth)), depth, depth, depth, 1, GraphicsUnit.Pixel);
                    //bottom left corner
                    tg.DrawImage(patternbm, new Rectangle(rc.Right - depth, rc.Bottom - depth, depth, depth), depth, depth, depth, depth, GraphicsUnit.Pixel);
                    //bottom side
                    tg.DrawImage(patternbm, new Rectangle(rc.Left + depth, rc.Bottom - depth, rc.Width - (2 * depth), depth), depth, depth, 1, depth, GraphicsUnit.Pixel);
                    //bottom left corner
                    tg.DrawImage(patternbm, new Rectangle(rc.Left, rc.Bottom - depth, depth, depth), 0, depth, depth, depth, GraphicsUnit.Pixel);
                    //left side
                    tg.DrawImage(patternbm, new Rectangle(rc.Left, rc.Top + depth, depth, rc.Height - (2 * depth)), 0, depth, depth, 1, GraphicsUnit.Pixel);
                }
            }
        }
    }

    unsafe public class FastBitmap {
        public FastBitmap(Bitmap inputBitmap) {
            workingBitmap = inputBitmap;
        }

        private int width = 0;
        private Bitmap workingBitmap = null;
        private BitmapData bitmapData = null;
        private Byte* pBase = null;

        [StructLayout(LayoutKind.Sequential)]
        private struct PixelData {
            public byte blue;
            public byte green;
            public byte red;
            public byte alpha;

            public override string ToString() {
                return "(" + alpha.ToString() + ", " + red.ToString() + ", " + green.ToString() + ", " + blue.ToString() + ")";
            }
        }

        public void LockImage() {
            //Size
            Rectangle bounds = new Rectangle(Point.Empty, workingBitmap.Size);
            width = (int)(bounds.Width * sizeof(PixelData));

            if (width % 4 != 0)
                width = 4 * (width / 4 + 1);

            //Lock Image
            bitmapData = workingBitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
            pBase = (Byte*)bitmapData.Scan0.ToPointer();
        }

        private PixelData* pixelData = null;

        public Color GetPixel(int x, int y) {
            pixelData = (PixelData*)(pBase + y * width + x * sizeof(PixelData));
            return Color.FromArgb(pixelData->alpha, pixelData->red, pixelData->green, pixelData->blue);
        }

        public Color GetPixelNext() {
            pixelData++;
            return Color.FromArgb(pixelData->alpha, pixelData->red, pixelData->green, pixelData->blue);
        }

        public void SetPixel(int x, int y, Color color) {
            PixelData* data = (PixelData*)(pBase + y * width + x * sizeof(PixelData));
            data->alpha = color.A;
            data->green = color.G;
            data->blue = color.B;
            data->red = color.R;
        }

        public void UnlockImage() {
            workingBitmap.UnlockBits(bitmapData);
            bitmapData = null;
            pBase = null;
        }

    }
}

Usage:

    private void button1_Click(object sender, EventArgs e) {
        ImageFormatter formatter = new ImageFormatter();
        Image output = formatter.GetCanvasedImage(
            @"C:\Development\input.jpg"
        );

        output.Save(
            @"C:\Development\output.jpg",
            System.Drawing.Imaging.ImageFormat.Jpeg);
    }

Good luck! I hope this is helpful.

Solution courtesy of: drankin2112

Discussion

View additional discussion.



This post first appeared on CSS3 Recipes - The Solution To All Your Style Problems, please read the originial post: here

Share the post

Ways to create streched canvas view

×

Subscribe to Css3 Recipes - The Solution To All Your Style Problems

Get updates delivered right to your inbox!

Thank you for your subscription

×