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.
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!
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With