12

Does anyone have an idea, link, library, source code, ... on how to convert photo's and images (bitmaps) to sketchy-like pictures? I can't find any good sources on how to do it.

I found this link How to cartoon-ify an image programmatically? about how to cartoon-ify a image programmatically, but i prefer to make it image-to-sketch one.

I want to make an android app that can programmatically "convert" JPEG photo's to sketchy images.

Community
  • 1
  • 1
Verhelst
  • 1,492
  • 2
  • 22
  • 46
  • Yea, i know that, but it's something else i'm making. However, i found an algorithm in C on how to do it using OpenCV: http://www.barbato.us/2010/12/22/a-quick-algorithm-to-turn-an-image-or-video-into-pencil-sketch-using-opencv/#toc-the-algorithm I will try to follow those instructions – Verhelst Mar 22 '12 at 16:40

5 Answers5

30

Ok, so i found my own answer using different techniques like Mark told me. I use the following pseudocode:

*s = Read-File-Into-Image("/path/to/image")
*g = Convert-To-Gray-Scale(s)
*i = Invert-Colors(g)
*b = Apply-Gaussian-Blur(i)
*result = Color-Dodge-Blend-Merge(b,g)

The first four methods were easily to find on the internet, however on the last one I couldn't find a lot of information, not even source code. So I searched on how PS did it and found the following formula in c++:

((uint8)((B == 255) ? B:min(255, ((A << 8 ) / (255 - B)))))

Then i converted it to Java with the following code:

private int colordodge(int in1, int in2) {
    float image = (float)in2;
    float mask = (float)in1;
    return ((int) ((image == 255) ? image:Math.min(255, (((long)mask << 8 ) / (255 - image)))));

}

/**
 * Blends 2 bitmaps to one and adds the color dodge blend mode to it.
 */
public Bitmap ColorDodgeBlend(Bitmap source, Bitmap layer) {
    Bitmap base = source.copy(Config.ARGB_8888, true);
    Bitmap blend = layer.copy(Config.ARGB_8888, false);

    IntBuffer buffBase = IntBuffer.allocate(base.getWidth() * base.getHeight());
    base.copyPixelsToBuffer(buffBase);
    buffBase.rewind();

    IntBuffer buffBlend = IntBuffer.allocate(blend.getWidth() * blend.getHeight());
    blend.copyPixelsToBuffer(buffBlend);
    buffBlend.rewind();

    IntBuffer buffOut = IntBuffer.allocate(base.getWidth() * base.getHeight());
    buffOut.rewind();

    while (buffOut.position() < buffOut.limit()) {
        int filterInt = buffBlend.get();
        int srcInt = buffBase.get();

        int redValueFilter = Color.red(filterInt);
        int greenValueFilter = Color.green(filterInt);
        int blueValueFilter = Color.blue(filterInt);

        int redValueSrc = Color.red(srcInt);
        int greenValueSrc = Color.green(srcInt);
        int blueValueSrc = Color.blue(srcInt);

        int redValueFinal = colordodge(redValueFilter, redValueSrc);
        int greenValueFinal = colordodge(greenValueFilter, greenValueSrc);
        int blueValueFinal = colordodge(blueValueFilter, blueValueSrc);

        int pixel = Color.argb(255, redValueFinal, greenValueFinal, blueValueFinal);

        buffOut.put(pixel);
    }

    buffOut.rewind();

    base.copyPixelsFromBuffer(buffOut);
    blend.recycle();

    return base;
}

If the code could be improved, please post a new answer or comment below. Thanks!

Verhelst
  • 1,492
  • 2
  • 22
  • 46
  • 1
    Its working fine but how to make the sketch more clear? Now its kind of hazy. any solution to make it more clear? – Junaid Nov 16 '12 at 13:26
  • @XverhelstX - Can you please post Apply-Gaussian-Blur code which you used? I tried some on stackoverflow but not getting good results. Thanks in advance! – NoviceMe Dec 04 '13 at 20:27
  • It's not look like perfect with this one https://play.google.com/store/apps/details?id=com.dumplingsandwich.pencilsketch..Please help me for this one. Thanks in advance – Denny Sharma Feb 11 '14 at 05:07
6

And adding color.

*s = Read-File-Into-Image("/path/to/image")
*g = Convert-To-Gray-Scale(s)
*i = Invert-Colors(g)
*b = Apply-Gaussian-Blur(i)
*result = Color-Dodge-Blend-Merge(b,g)   
*s2 = Apply-Gaussian-Blur(s) //I use radius 3
*cartoon = Apply-Color(s2, result)

I little modification to ColorDodgeBlend to eliminate all colors.

public Bitmap ColorDodgeBlend(Bitmap source, Bitmap layer) 
....
//before buffOut.put(pixel);

float[] hsv = new float[3];
        Color.colorToHSV(pixel, hsv);
        hsv[1] = 0.0f;
        float top = VALUE_TOP; //Between 0.0f .. 1.0f I use 0.87f
        if (hsv[2] <= top) {
            hsv[2] = 0.0f;
        } else {
            hsv[2] = 1.0f;
        }
        pixel = Color.HSVToColor(hsv);

An the applying color method:

//hue, saturarion, value intervals size are for reduce colors on Bitmap
//saturation, value percents are for increment or decrement [0..100..)
public Bitmap getCartoonizedBitmap(Bitmap realBitmap, Bitmap dodgeBlendBitmap, int hueIntervalSize, int saturationIntervalSize, int valueIntervalSize, int saturationPercent, int valuePercent) {
    // Bitmap bitmap = Bitmap.createBitmap(scaledBitmap);
    // //fastblur(scaledBitmap, 4);
    Bitmap base = fastblur(realBitmap, 3).copy(Config.ARGB_8888, true);
    Bitmap dodge = dodgeBlendBitmap.copy(Config.ARGB_8888, false);
    try {
        int realColor;
        int color;
        float top = VALUE_TOP; //Between 0.0f .. 1.0f I use 0.87f
        IntBuffer templatePixels = IntBuffer.allocate(dodge.getWidth()
                * dodge.getHeight());
        IntBuffer scaledPixels = IntBuffer.allocate(base.getWidth()
                * base.getHeight());
        IntBuffer buffOut = IntBuffer.allocate(base.getWidth()
                * base.getHeight());

        base.copyPixelsToBuffer(scaledPixels);
        dodge.copyPixelsToBuffer(templatePixels);

        templatePixels.rewind();
        scaledPixels.rewind();
        buffOut.rewind();

        while (buffOut.position() < buffOut.limit()) {
            color = (templatePixels.get());
            realColor = scaledPixels.get();

            float[] realHSV = new float[3];
            Color.colorToHSV(realColor, realHSV);

            realHSV[0] = getRoundedValue(realHSV[0], hueIntervalSize);

            realHSV[2] = (getRoundedValue(realHSV[2] * 100,
                    valueIntervalSize) / 100) * (valuePercent / 100);
            realHSV[2] = realHSV[2]<1.0?realHSV[2]:1.0f;

            realHSV[1] = realHSV[1] * (saturationPercent / 100);
            realHSV[1] = realHSV[1]<1.0?realHSV[1]:1.0f;

            float[] HSV = new float[3];
            Color.colorToHSV(color, HSV);

            boolean putBlackPixel = HSV[2] <= top;

            realColor = Color.HSVToColor(realHSV);

            if (putBlackPixel) {
                buffOut.put(color);
            } else {
                buffOut.put(realColor);
            }
        }// END WHILE
        dodge.recycle();
        buffOut.rewind();
        base.copyPixelsFromBuffer(buffOut); 

    } catch (Exception e) {
        // TODO: handle exception
    }

    return base;
}

public static float getRoundedValue(float value, int intervalSize) {
        float result = Math.round(value);
        int mod = ((int) result) % intervalSize;
        result += mod < (intervalSize / 2) ? -mod : intervalSize - mod;
        return result;

    }

This is not improved. Its better if Apply-Color and Color-Dodge-Blend-Merge merges.

Thanks to XverhelstX for his Question-Answer

Rabamirezzan
  • 87
  • 1
  • 5
4

Here is the clear answer with the code according to @XverhelstX I have created a code to get clearly sketch from photo. Take Image from source and convert it into an Input Bitmap. Now call the method below and pass Bitmap into it.

public Bitmap Changetosketch(Bitmap bmp){
    Bitmap Copy,Invert,Result;
    Copy =bmp;
    Copy = toGrayscale(Copy);
    Invert = createInvertedBitmap(Copy);
    Invert = Blur.blur(MainActivity.this, Invert);
    Result = ColorDodgeBlend(Invert, Copy);

    return Result;
}

Here we got 3 methods GrayScale,Inverted,and Color-DodgeBlend.

public static Bitmap toGrayscale(Bitmap bmpOriginal)
{
    int width, height;
    height = bmpOriginal.getHeight();
    width = bmpOriginal.getWidth();

    Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
    Canvas c = new Canvas(bmpGrayscale);
    Paint paint = new Paint();
    ColorMatrix cm = new ColorMatrix();
    cm.setSaturation(0);
    ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
    paint.setColorFilter(f);
    c.drawBitmap(bmpOriginal, 0, 0, paint);
    return bmpGrayscale;
}

public static Bitmap createInvertedBitmap(Bitmap src) {
    ColorMatrix colorMatrix_Inverted =
            new ColorMatrix(new float[] {
                    -1,  0,  0,  0, 255,
                    0, -1,  0,  0, 255,
                    0,  0, -1,  0, 255,
                    0,  0,  0,  1,   0});

    ColorFilter ColorFilter_Sepia = new ColorMatrixColorFilter(
            colorMatrix_Inverted);

    Bitmap bitmap = Bitmap.createBitmap(src.getWidth(), src.getHeight(),
            Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);

    Paint paint = new Paint();

    paint.setColorFilter(ColorFilter_Sepia);
    canvas.drawBitmap(src, 0, 0, paint);

    return bitmap;
}
public Bitmap ColorDodgeBlend(Bitmap source, Bitmap layer) {
    Bitmap base = source.copy(Bitmap.Config.ARGB_8888, true);
    Bitmap blend = layer.copy(Bitmap.Config.ARGB_8888, false);

    IntBuffer buffBase = IntBuffer.allocate(base.getWidth() * base.getHeight());
    base.copyPixelsToBuffer(buffBase);
    buffBase.rewind();

    IntBuffer buffBlend = IntBuffer.allocate(blend.getWidth() * blend.getHeight());
    blend.copyPixelsToBuffer(buffBlend);
    buffBlend.rewind();

    IntBuffer buffOut = IntBuffer.allocate(base.getWidth() * base.getHeight());
    buffOut.rewind();

    while (buffOut.position() < buffOut.limit()) {

        int filterInt = buffBlend.get();
        int srcInt = buffBase.get();

        int redValueFilter = Color.red(filterInt);
        int greenValueFilter = Color.green(filterInt);
        int blueValueFilter = Color.blue(filterInt);

        int redValueSrc = Color.red(srcInt);
        int greenValueSrc = Color.green(srcInt);
        int blueValueSrc = Color.blue(srcInt);

        int redValueFinal = colordodge(redValueFilter, redValueSrc);
        int greenValueFinal = colordodge(greenValueFilter, greenValueSrc);
        int blueValueFinal = colordodge(blueValueFilter, blueValueSrc);


        int pixel = Color.argb(255, redValueFinal, greenValueFinal, blueValueFinal);


        buffOut.put(pixel);
    }

    buffOut.rewind();

    base.copyPixelsFromBuffer(buffOut);
    blend.recycle();

    return base;
}

private int colordodge(int in1, int in2) {
    float image = (float)in2;
    float mask = (float)in1;
    return ((int) ((image == 255) ? image:Math.min(255, (((long)mask << 8 ) / (255 - image)))));
}

One thing to be noted that in my code I am blurring the bitmap using Renderscript.

Here is the Blur class.

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.support.v8.renderscript.Allocation;
import android.support.v8.renderscript.Element;
import android.support.v8.renderscript.RenderScript;
import android.support.v8.renderscript.ScriptIntrinsicBlur;
import android.view.View;


public class Blur {
    private static final float BITMAP_SCALE = 0.4f;
    private static final float BLUR_RADIUS = 4.5f;

    public static Bitmap blur(View v) {
        return blur(v.getContext(), getScreenshot(v));
    }

    public static Bitmap blur(Context ctx, Bitmap image) {
        Bitmap photo = image.copy(Bitmap.Config.ARGB_8888, true);

        try {
            final RenderScript rs = RenderScript.create( ctx );
            final Allocation input = Allocation.createFromBitmap(rs, photo, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
            final Allocation output = Allocation.createTyped(rs, input.getType());
            final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
            script.setRadius( BLUR_RADIUS ); /* e.g. 3.f */
            script.setInput( input );
            script.forEach( output );
            output.copyTo( photo );
        }catch (Exception e){
            e.printStackTrace();
        }
        return photo;
    }

    private static Bitmap getScreenshot(View v) {
        Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(b);
        v.draw(c);
        return b;
    }
}

After setting all this, simply pass your input bitmap into the first method from onCreate method i-e:

Done.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {

      ResultBitmap = ChangetoSketch(InputBitmap);
      ImageView.setImageBitmap(ResultBitmap);

    }
});
Pang
  • 9,564
  • 146
  • 81
  • 122
Dear S
  • 51
  • 3
4

Here's an example of how to create such an effect in a graphics editing program:

http://www.createblog.com/paintshop-pro-tutorials/14018-sketch-effect/

  1. Convert the image to grayscale.
  2. Make a copy and invert the intensities.
  3. Blur the copy.
  4. Combine the two images using a Color Dodge formula.
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
1

Ok if you got one then you can post the code here and see if someone can help you translate the code to java ..the other alternative being..you may have to use the ndk perhaps..However I did find some links and I am posting them here..hope you find something interesting here in these links

How to cartoon-ify an image programmatically? you can check this link

Community
  • 1
  • 1
Nav
  • 10,304
  • 20
  • 56
  • 83
  • Hey, thanks for your reply. I already had that link in my post above. I might already have found a way to do it. I will let you know how i've done it. – Verhelst Mar 22 '12 at 16:55
  • ok mate..paste the answer if you got 1 and let others know if they come across such a problem – Nav Mar 22 '12 at 16:58
  • Have anyone an idea of how to do cartoons image in android – Bhaven Shah Oct 20 '21 at 11:21