@ -50,13 +50,13 @@ namespace ImageProcessor.Imaging.Filters
/// </summary>
Alpha = 3
}
/// <summary>
/// Gets the <see cref="T:System.Drawing.Imaging.ColorMatrix"/> for this filter instance.
/// </summary>
public ColorMatrix Matrix
{
get { return ColorMatrixes . LoSatch ; }
get { return ColorMatrixes . ComicLow ; }
}
/// <summary>
@ -74,61 +74,80 @@ namespace ImageProcessor.Imaging.Filters
public Image TransformImage ( ImageFactory factory , Image image , Image newImage )
{
// Bitmaps for comic pattern
Bitmap hisatchBitmap = null ;
Bitmap highBitmap = null ;
Bitmap lowBitmap = null ;
Bitmap patternBitmap = null ;
try
{
using ( Graphics graphics = Graphics . FromImage ( newImage ) )
using ( ImageAttributes attributes = new ImageAttributes ( ) )
{
using ( ImageAttributes attributes = new ImageAttributes ( ) )
{
attributes . SetColorMatrix ( this . Matrix ) ;
Rectangle rectangle = new Rectangle ( 0 , 0 , image . Width , image . Height ) ;
Rectangle rectangle = new Rectangle ( 0 , 0 , image . Width , image . Height ) ;
attributes . SetColorMatrix ( ColorMatrixes . ComicHigh ) ;
// Set the attributes to LoSatch and draw the image .
graphics . DrawImage ( image , rectangle , 0 , 0 , image . Width , imag e . Height , GraphicsUnit . Pixel , attributes ) ;
// Draw the image with the high comic colormatrix .
highBitmap = new Bitmap ( rectangle . Width , rectangl e. Height , PixelFormat . Format32bppPArgb ) ;
// Create a bitmap for overlaying .
hisatchBitmap = new Bitmap ( rectangle . Width , rectangle . Height , PixelFormat . Format32bppPArgb ) ;
// Apply a oil painting filter to the image .
highBitmap = OilPaintFilter ( ( Bitmap ) image , 3 , 5 ) ;
// Set the color matrix
attributes . SetColorMatrix ( ColorMatrixes . HiSatch ) ;
using ( Graphics graphics = Graphics . FromImage ( highBitmap ) )
{
graphics . DrawImage ( highBitmap , rectangle , 0 , 0 , image . Width , image . Height , GraphicsUnit . Pixel , attributes ) ;
}
// Draw the image with the hisatch colormatrix.
using ( var g = Graphics . FromImage ( hisatchBitmap ) )
{
g . DrawImage ( image , rectangle , 0 , 0 , image . Width , image . Height , GraphicsUnit . Pixel , attributes ) ;
}
// Create a bitmap for overlaying.
lowBitmap = new Bitmap ( rectangle . Width , rectangle . Height , PixelFormat . Format32bppPArgb ) ;
// Set the color matrix
attributes . SetColorMatrix ( this . Matrix ) ;
// Draw the image with the losatch colormatrix.
using ( Graphics graphics = Graphics . FromImage ( lowBitmap ) )
{
graphics . DrawImage ( highBitmap , rectangle , 0 , 0 , image . Width , image . Height , GraphicsUnit . Pixel , attributes ) ;
}
// We need to create a new image now with the hi saturation colormatrix and a pattern mask to paint it
// onto the other image with.
patternBitmap = new Bitmap ( rectangle . Width , rectangle . Height , PixelFormat . Format32bppPArgb ) ;
// We need to create a new image now with a pattern mask to paint it
// onto the other image with.
patternBitmap = new Bitmap ( rectangle . Width , rectangle . Height , PixelFormat . Format32bppPArgb ) ;
// Create the pattern mask.
using ( var g = Graphics . FromImage ( patternBitmap ) )
// Create the pattern mask.
using ( Graphics graphics = Graphics . FromImage ( patternBitmap ) )
{
graphics . Clear ( Color . Black ) ;
graphics . SmoothingMode = SmoothingMode . HighQuality ;
for ( int y = 0 ; y < image . Height ; y + = 8 )
{
g . Clear ( Color . Black ) ;
g . SmoothingMode = SmoothingMode . HighQuality ;
for ( var y = 0 ; y < image . Height ; y + = 1 0 )
for ( int x = 0 ; x < image . Width ; x + = 4 )
{
for ( var x = 0 ; x < image . Width ; x + = 6 )
{
g . FillEllipse ( Brushes . White , x , y , 4 , 4 ) ;
g . FillEllipse ( Brushes . White , x + 3 , y + 5 , 4 , 4 ) ;
}
graphics . FillEllipse ( Brushes . White , x , y , 3 , 3 ) ;
graphics . FillEllipse ( Brushes . White , x + 2 , y + 4 , 3 , 3 ) ;
}
}
}
// Transfer the alpha channel from the mask to the high saturation image.
TransferOneArgbChannelFromOneBitmapToAnother ( patternBitmap , hisatch Bitmap, ChannelArgb . Blue , ChannelArgb . Alpha ) ;
// Transfer the alpha channel from the mask to the high saturation image.
TransferOneArgbChannelFromOneBitmapToAnother ( patternBitmap , low Bitmap, ChannelArgb . Blue , ChannelArgb . Alpha ) ;
using ( Graphics graphics = Graphics . FromImage ( newImage ) )
{
// Overlay the image.
graphics . DrawImage ( hisatchBitmap , 0 , 0 ) ;
graphics . DrawImage ( highBitmap , 0 , 0 ) ;
graphics . DrawImage ( lowBitmap , 0 , 0 ) ;
// Draw an edge around the image.
using ( Pen blackPen = new Pen ( Color . Black ) )
{
blackPen . Width = 4 ;
graphics . DrawRectangle ( blackPen , rectangle ) ;
}
// Dispose of the other images
hisatchBitmap . Dispose ( ) ;
highBitmap . Dispose ( ) ;
lowBitmap . Dispose ( ) ;
patternBitmap . Dispose ( ) ;
}
}
@ -144,9 +163,14 @@ namespace ImageProcessor.Imaging.Filters
newImage . Dispose ( ) ;
}
if ( hisatc hBitmap ! = null )
if ( hig hBitmap ! = null )
{
hisatchBitmap . Dispose ( ) ;
highBitmap . Dispose ( ) ;
}
if ( lowBitmap ! = null )
{
lowBitmap . Dispose ( ) ;
}
if ( patternBitmap ! = null )
@ -158,6 +182,335 @@ namespace ImageProcessor.Imaging.Filters
return image ;
}
/// <summary>
/// Applies an oil paint filter.
/// TODO: Move this to another class and add to the factory
/// </summary>
/// <param name="sourceBitmap">
/// The source bitmap.
/// </param>
/// <param name="levels">
/// The levels.
/// </param>
/// <param name="filterSize">
/// The filter size.
/// </param>
/// <returns>
/// The <see cref="Bitmap"/>.
/// </returns>
private static Bitmap OilPaintFilter ( Bitmap sourceBitmap , int levels , int filterSize )
{
int width = sourceBitmap . Width ;
int height = sourceBitmap . Height ;
BitmapData sourceData = sourceBitmap . LockBits (
new Rectangle ( 0 , 0 , width , height ) ,
ImageLockMode . ReadOnly ,
PixelFormat . Format32bppArgb ) ;
int strideWidth = sourceData . Stride ;
int scanHeight = sourceData . Height ;
int bufferSize = strideWidth * scanHeight ;
byte [ ] pixelBuffer = new byte [ bufferSize ] ;
byte [ ] resultBuffer = new byte [ bufferSize ] ;
Marshal . Copy ( sourceData . Scan0 , pixelBuffer , 0 , pixelBuffer . Length ) ;
sourceBitmap . UnlockBits ( sourceData ) ;
levels = levels - 1 ;
int radius = filterSize > > 1 ;
for ( int y = 0 ; y < height ; y + + )
{
for ( int x = 0 ; x < width ; x + + )
{
int maxIntensity = 0 ;
int maxIndex = 0 ;
int [ ] intensityBin = new int [ levels + 1 ] ;
int [ ] blueBin = new int [ levels + 1 ] ;
int [ ] greenBin = new int [ levels + 1 ] ;
int [ ] redBin = new int [ levels + 1 ] ;
int byteOffset = ( y * strideWidth ) + ( x * 4 ) ;
for ( int i = 0 ; i < = radius ; i + + )
{
int ir = i - radius ;
int offsetY = y + ir ;
// Skip the current row
if ( offsetY < 0 )
{
continue ;
}
// Outwith the current bounds so break.
if ( offsetY > = height )
{
break ;
}
for ( int j = 0 ; j < = radius ; j + + )
{
int jr = j - radius ;
int offsetX = x + jr ;
// Skip the column
if ( offsetX < 0 )
{
continue ;
}
if ( offsetX < width )
{
int calcOffset = ( offsetX * 4 ) + ( offsetY * sourceData . Stride ) ;
byte sourceBlue = pixelBuffer [ calcOffset ] ;
byte sourceGreen = pixelBuffer [ calcOffset + 1 ] ;
byte sourceRed = pixelBuffer [ calcOffset + 2 ] ;
int currentIntensity = ( int ) Math . Round ( ( ( sourceBlue + sourceGreen + sourceRed ) / 3.0 * levels ) / 2 5 5.0 ) ;
intensityBin [ currentIntensity ] + = 1 ;
blueBin [ currentIntensity ] + = sourceBlue ;
greenBin [ currentIntensity ] + = sourceGreen ;
redBin [ currentIntensity ] + = sourceRed ;
if ( intensityBin [ currentIntensity ] > maxIntensity )
{
maxIntensity = intensityBin [ currentIntensity ] ;
maxIndex = currentIntensity ;
}
}
}
}
double blue = Math . Abs ( blueBin [ maxIndex ] / maxIntensity ) ;
double green = Math . Abs ( greenBin [ maxIndex ] / maxIntensity ) ;
double red = Math . Abs ( redBin [ maxIndex ] / maxIntensity ) ;
blue = blue > 2 5 5 ? 2 5 5 : ( blue < 0 ? 0 : blue ) ;
green = green > 2 5 5 ? 2 5 5 : ( green < 0 ? 0 : green ) ;
red = red > 2 5 5 ? 2 5 5 : ( red < 0 ? 0 : red ) ;
resultBuffer [ byteOffset ] = ( byte ) blue ;
resultBuffer [ byteOffset + 1 ] = ( byte ) green ;
resultBuffer [ byteOffset + 2 ] = ( byte ) red ;
resultBuffer [ byteOffset + 3 ] = 2 5 5 ;
}
}
Bitmap resultBitmap = new Bitmap ( width , height ) ;
BitmapData resultData = resultBitmap . LockBits (
new Rectangle ( 0 , 0 , width , height ) ,
ImageLockMode . WriteOnly ,
PixelFormat . Format32bppArgb ) ;
Marshal . Copy ( resultBuffer , 0 , resultData . Scan0 , resultBuffer . Length ) ;
resultBitmap . UnlockBits ( resultData ) ;
return resultBitmap ;
}
/// <summary>
/// Detects and draws edges.
/// TODO: Move this to another class and do move edge detection.
/// </summary>
/// <param name="sourceBitmap">
/// The source bitmap.
/// </param>
/// <param name="threshold">
/// The threshold.
/// </param>
/// <returns>
/// The <see cref="Bitmap"/>.
/// </returns>
private static Bitmap DrawEdges ( Bitmap sourceBitmap , byte threshold = 0 )
{
Color color = Color . Black ;
int width = sourceBitmap . Width ;
int height = sourceBitmap . Height ;
BitmapData sourceData = sourceBitmap . LockBits (
new Rectangle ( 0 , 0 , width , height ) ,
ImageLockMode . ReadOnly ,
PixelFormat . Format32bppArgb ) ;
int strideWidth = sourceData . Stride ;
int scanHeight = sourceData . Height ;
int bufferSize = strideWidth * scanHeight ;
byte [ ] pixelBuffer = new byte [ bufferSize ] ;
byte [ ] resultBuffer = new byte [ bufferSize ] ;
Marshal . Copy ( sourceData . Scan0 , pixelBuffer , 0 , pixelBuffer . Length ) ;
sourceBitmap . UnlockBits ( sourceData ) ;
for ( int offsetY = 1 ; offsetY < height - 1 ; offsetY + + )
{
for ( int offsetX = 1 ; offsetX < width - 1 ; offsetX + + )
{
int byteOffset = ( offsetY * strideWidth ) + ( offsetX * 4 ) ;
int blueGradient = Math . Abs ( pixelBuffer [ byteOffset - 4 ] - pixelBuffer [ byteOffset + 4 ] ) ;
blueGradient + =
Math . Abs (
pixelBuffer [ byteOffset - strideWidth ] - pixelBuffer [ byteOffset + strideWidth ] ) ;
byteOffset + + ;
int greenGradient = Math . Abs ( pixelBuffer [ byteOffset - 4 ] - pixelBuffer [ byteOffset + 4 ] ) ;
greenGradient + =
Math . Abs (
pixelBuffer [ byteOffset - strideWidth ] - pixelBuffer [ byteOffset + strideWidth ] ) ;
byteOffset + + ;
int redGradient = Math . Abs ( pixelBuffer [ byteOffset - 4 ] - pixelBuffer [ byteOffset + 4 ] ) ;
redGradient + =
Math . Abs (
pixelBuffer [ byteOffset - strideWidth ] - pixelBuffer [ byteOffset + strideWidth ] ) ;
bool exceedsThreshold ;
if ( blueGradient + greenGradient + redGradient > threshold )
{
exceedsThreshold = true ;
}
else
{
byteOffset - = 2 ;
blueGradient = Math . Abs ( pixelBuffer [ byteOffset - 4 ] - pixelBuffer [ byteOffset + 4 ] ) ;
byteOffset + + ;
greenGradient = Math . Abs ( pixelBuffer [ byteOffset - 4 ] - pixelBuffer [ byteOffset + 4 ] ) ;
byteOffset + + ;
redGradient = Math . Abs ( pixelBuffer [ byteOffset - 4 ] - pixelBuffer [ byteOffset + 4 ] ) ;
if ( blueGradient + greenGradient + redGradient > threshold )
{
exceedsThreshold = true ;
}
else
{
byteOffset - = 2 ;
blueGradient =
Math . Abs ( pixelBuffer [ byteOffset - strideWidth ] - pixelBuffer [ byteOffset + strideWidth ] ) ;
byteOffset + + ;
greenGradient =
Math . Abs ( pixelBuffer [ byteOffset - strideWidth ] - pixelBuffer [ byteOffset + strideWidth ] ) ;
byteOffset + + ;
redGradient =
Math . Abs ( pixelBuffer [ byteOffset - strideWidth ] - pixelBuffer [ byteOffset + strideWidth ] ) ;
if ( blueGradient + greenGradient + redGradient > threshold )
{
exceedsThreshold = true ;
}
else
{
byteOffset - = 2 ;
blueGradient =
Math . Abs (
pixelBuffer [ byteOffset - 4 - strideWidth ]
- pixelBuffer [ byteOffset + 4 + strideWidth ] ) ;
blueGradient + =
Math . Abs (
pixelBuffer [ byteOffset - strideWidth + 4 ]
- pixelBuffer [ byteOffset + strideWidth - 4 ] ) ;
byteOffset + + ;
greenGradient =
Math . Abs (
pixelBuffer [ byteOffset - 4 - strideWidth ]
- pixelBuffer [ byteOffset + 4 + strideWidth ] ) ;
greenGradient + =
Math . Abs (
pixelBuffer [ byteOffset - strideWidth + 4 ]
- pixelBuffer [ byteOffset + strideWidth - 4 ] ) ;
byteOffset + + ;
redGradient =
Math . Abs (
pixelBuffer [ byteOffset - 4 - strideWidth ]
- pixelBuffer [ byteOffset + 4 + strideWidth ] ) ;
redGradient + =
Math . Abs (
pixelBuffer [ byteOffset - strideWidth + 4 ]
- pixelBuffer [ byteOffset + strideWidth - 4 ] ) ;
exceedsThreshold = blueGradient + greenGradient + redGradient > threshold ;
}
}
}
byteOffset - = 2 ;
double blue ;
double red ;
double green ;
double alpha ;
if ( exceedsThreshold )
{
blue = color . B ; // 0;
green = color . G ; // 0;
red = color . R ; // 0;
alpha = 2 5 5 ;
}
else
{
// These would normally be used to transfer the correct value accross.
// blue = pixelBuffer[byteOffset];
// green = pixelBuffer[byteOffset + 1];
// red = pixelBuffer[byteOffset + 2];
blue = 2 5 5 ;
green = 2 5 5 ;
red = 2 5 5 ;
alpha = 0 ;
}
blue = blue > 2 5 5 ? 2 5 5 : ( blue < 0 ? 0 : blue ) ;
green = green > 2 5 5 ? 2 5 5 : ( green < 0 ? 0 : green ) ;
red = red > 2 5 5 ? 2 5 5 : ( red < 0 ? 0 : red ) ;
resultBuffer [ byteOffset ] = ( byte ) blue ;
resultBuffer [ byteOffset + 1 ] = ( byte ) green ;
resultBuffer [ byteOffset + 2 ] = ( byte ) red ;
resultBuffer [ byteOffset + 3 ] = ( byte ) alpha ;
}
}
Bitmap resultBitmap = new Bitmap ( width , height ) ;
BitmapData resultData = resultBitmap . LockBits (
new Rectangle ( 0 , 0 , resultBitmap . Width , resultBitmap . Height ) ,
ImageLockMode . WriteOnly ,
PixelFormat . Format32bppArgb ) ;
Marshal . Copy ( resultBuffer , 0 , resultData . Scan0 , resultBuffer . Length ) ;
resultBitmap . UnlockBits ( resultData ) ;
return resultBitmap ;
}
/// <summary>
/// Transfers a single ARGB channel from one image to another.
/// </summary>
@ -211,6 +564,7 @@ namespace ImageProcessor.Imaging.Filters
for ( int i = rectangle . Height * rectangle . Width ; i > 0 ; i - - )
{
// Copy the alpha values across.
destinationRgbValues [ d ] = sourceRgbValues [ s ] ;
d + = 4 ;
s + = 4 ;
@ -223,4 +577,4 @@ namespace ImageProcessor.Imaging.Filters
destination . UnlockBits ( bitmapDataDestination ) ;
}
}
}
}