mirror of
https://github.com/greenshot/greenshot
synced 2025-07-15 01:23:47 -07:00
Fixing some bitmap code to work, this should improve the quality a bit. Also re-factored some code so I could add more professionally shadows.
git-svn-id: http://svn.code.sf.net/p/greenshot/code/trunk@1639 7dccd23d-a4a3-4e1f-8c07-b4c1b4018ab4
This commit is contained in:
parent
a52e83dc45
commit
ab7522d743
7 changed files with 360 additions and 253 deletions
|
@ -63,25 +63,8 @@ namespace Greenshot.Drawing.Filters {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method fixes the problem that we can't apply a filter outside the target bitmap,
|
|
||||||
* therefor the filtered-bitmap will be shifted if we try to draw it outside the target bitmap.
|
|
||||||
* It will also account for the Invert flag.
|
|
||||||
*/
|
|
||||||
protected Rectangle IntersectRectangle(Size applySize, Rectangle rect) {
|
|
||||||
Rectangle myRect;
|
|
||||||
if (Invert) {
|
|
||||||
myRect = new Rectangle(0, 0, applySize.Width, applySize.Height);
|
|
||||||
} else {
|
|
||||||
Rectangle applyRect = new Rectangle(0,0, applySize.Width, applySize.Height);
|
|
||||||
myRect = new Rectangle(rect.X, rect.Y, rect.Width, rect.Height);
|
|
||||||
myRect.Intersect(applyRect);
|
|
||||||
}
|
|
||||||
return myRect;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Apply(Graphics graphics, Bitmap applyBitmap, Rectangle rect, RenderMode renderMode) {
|
public virtual void Apply(Graphics graphics, Bitmap applyBitmap, Rectangle rect, RenderMode renderMode) {
|
||||||
applyRect = IntersectRectangle(applyBitmap.Size, rect);
|
applyRect = ImageHelper.CreateIntersectRectangle(applyBitmap.Size, rect, Invert);
|
||||||
|
|
||||||
if (applyRect.Width == 0 || applyRect.Height == 0) {
|
if (applyRect.Width == 0 || applyRect.Height == 0) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
|
|
|
@ -20,9 +20,10 @@ using System.Drawing;
|
||||||
using Greenshot.Drawing.Fields;
|
using Greenshot.Drawing.Fields;
|
||||||
using Greenshot.Plugin.Drawing;
|
using Greenshot.Plugin.Drawing;
|
||||||
using GreenshotPlugin.Core;
|
using GreenshotPlugin.Core;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
|
||||||
namespace Greenshot.Drawing.Filters {
|
namespace Greenshot.Drawing.Filters {
|
||||||
[Serializable()]
|
[Serializable()]
|
||||||
public class BlurFilter : AbstractFilter {
|
public class BlurFilter : AbstractFilter {
|
||||||
public double previewQuality;
|
public double previewQuality;
|
||||||
public double PreviewQuality {
|
public double PreviewQuality {
|
||||||
|
@ -35,201 +36,12 @@ namespace Greenshot.Drawing.Filters {
|
||||||
AddField(GetType(), FieldType.PREVIEW_QUALITY, 1.0d);
|
AddField(GetType(), FieldType.PREVIEW_QUALITY, 1.0d);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int[] CreateGaussianBlurRow(int amount) {
|
public unsafe override void Apply(Graphics graphics, Bitmap applyBitmap, Rectangle rect, RenderMode renderMode) {
|
||||||
int size = 1 + (amount * 2);
|
int blurRadius = GetFieldValueAsInt(FieldType.BLUR_RADIUS);
|
||||||
int[] weights = new int[size];
|
double previewQuality = GetFieldValueAsDouble(FieldType.PREVIEW_QUALITY);
|
||||||
|
|
||||||
for (int i = 0; i <= amount; ++i)
|
ImageHelper.ApplyBlur(graphics, applyBitmap, rect, renderMode == RenderMode.EXPORT, blurRadius, previewQuality, Invert, parent.Bounds);
|
||||||
{
|
return;
|
||||||
// 1 + aa - aa + 2ai - ii
|
|
||||||
weights[i] = 16 * (i + 1);
|
|
||||||
weights[weights.Length - i - 1] = weights[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return weights;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe override void Apply(Graphics graphics, Bitmap applyBitmap, Rectangle rect, RenderMode renderMode) {
|
|
||||||
applyRect = IntersectRectangle(applyBitmap.Size, rect);
|
|
||||||
|
|
||||||
if (applyRect.Height <= 0 || applyRect.Width <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int blurRadius = GetFieldValueAsInt(FieldType.BLUR_RADIUS);
|
|
||||||
double previewQuality = GetFieldValueAsDouble(FieldType.PREVIEW_QUALITY);
|
|
||||||
|
|
||||||
// do nothing when nothing can be done!
|
|
||||||
if (blurRadius < 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using (BitmapBuffer bbbDest = new BitmapBuffer(applyBitmap, applyRect)) {
|
|
||||||
bbbDest.Lock();
|
|
||||||
using (BitmapBuffer bbbSrc = new BitmapBuffer(applyBitmap, applyRect)) {
|
|
||||||
bbbSrc.Lock();
|
|
||||||
Random rand = new Random();
|
|
||||||
|
|
||||||
int r = blurRadius;
|
|
||||||
int[] w = CreateGaussianBlurRow(r);
|
|
||||||
int wlen = w.Length;
|
|
||||||
long[] waSums = new long[wlen];
|
|
||||||
long[] wcSums = new long[wlen];
|
|
||||||
long[] aSums = new long[wlen];
|
|
||||||
long[] bSums = new long[wlen];
|
|
||||||
long[] gSums = new long[wlen];
|
|
||||||
long[] rSums = new long[wlen];
|
|
||||||
for (int y = 0; y < applyRect.Height; ++y) {
|
|
||||||
long waSum = 0;
|
|
||||||
long wcSum = 0;
|
|
||||||
long aSum = 0;
|
|
||||||
long bSum = 0;
|
|
||||||
long gSum = 0;
|
|
||||||
long rSum = 0;
|
|
||||||
|
|
||||||
for (int wx = 0; wx < wlen; ++wx) {
|
|
||||||
int srcX = wx - r;
|
|
||||||
waSums[wx] = 0;
|
|
||||||
wcSums[wx] = 0;
|
|
||||||
aSums[wx] = 0;
|
|
||||||
bSums[wx] = 0;
|
|
||||||
gSums[wx] = 0;
|
|
||||||
rSums[wx] = 0;
|
|
||||||
|
|
||||||
if (srcX >= 0 && srcX < bbbDest.Width) {
|
|
||||||
for (int wy = 0; wy < wlen; ++wy) {
|
|
||||||
int srcY = y + wy - r;
|
|
||||||
|
|
||||||
if (srcY >= 0 && srcY < bbbDest.Height) {
|
|
||||||
int[] colors = bbbSrc.GetColorArrayAt(srcX, srcY);
|
|
||||||
int wp = w[wy];
|
|
||||||
|
|
||||||
waSums[wx] += wp;
|
|
||||||
wp *= colors[0] + (colors[0] >> 7);
|
|
||||||
wcSums[wx] += wp;
|
|
||||||
wp >>= 8;
|
|
||||||
|
|
||||||
aSums[wx] += wp * colors[0];
|
|
||||||
bSums[wx] += wp * colors[3];
|
|
||||||
gSums[wx] += wp * colors[2];
|
|
||||||
rSums[wx] += wp * colors[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int wwx = w[wx];
|
|
||||||
waSum += wwx * waSums[wx];
|
|
||||||
wcSum += wwx * wcSums[wx];
|
|
||||||
aSum += wwx * aSums[wx];
|
|
||||||
bSum += wwx * bSums[wx];
|
|
||||||
gSum += wwx * gSums[wx];
|
|
||||||
rSum += wwx * rSums[wx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wcSum >>= 8;
|
|
||||||
|
|
||||||
if (waSum == 0 || wcSum == 0) {
|
|
||||||
SetColorAt(bbbDest, 0, y, new int[]{0,0,0,0});
|
|
||||||
} else {
|
|
||||||
int alpha = (int)(aSum / waSum);
|
|
||||||
int blue = (int)(bSum / wcSum);
|
|
||||||
int green = (int)(gSum / wcSum);
|
|
||||||
int red = (int)(rSum / wcSum);
|
|
||||||
|
|
||||||
SetColorAt(bbbDest, 0, y, new int[]{alpha, red, green, blue});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int x = 1; x < applyRect.Width; ++x) {
|
|
||||||
for (int i = 0; i < wlen - 1; ++i) {
|
|
||||||
waSums[i] = waSums[i + 1];
|
|
||||||
wcSums[i] = wcSums[i + 1];
|
|
||||||
aSums[i] = aSums[i + 1];
|
|
||||||
bSums[i] = bSums[i + 1];
|
|
||||||
gSums[i] = gSums[i + 1];
|
|
||||||
rSums[i] = rSums[i + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
waSum = 0;
|
|
||||||
wcSum = 0;
|
|
||||||
aSum = 0;
|
|
||||||
bSum = 0;
|
|
||||||
gSum = 0;
|
|
||||||
rSum = 0;
|
|
||||||
|
|
||||||
int wx;
|
|
||||||
for (wx = 0; wx < wlen - 1; ++wx) {
|
|
||||||
long wwx = (long)w[wx];
|
|
||||||
waSum += wwx * waSums[wx];
|
|
||||||
wcSum += wwx * wcSums[wx];
|
|
||||||
aSum += wwx * aSums[wx];
|
|
||||||
bSum += wwx * bSums[wx];
|
|
||||||
gSum += wwx * gSums[wx];
|
|
||||||
rSum += wwx * rSums[wx];
|
|
||||||
}
|
|
||||||
|
|
||||||
wx = wlen - 1;
|
|
||||||
|
|
||||||
waSums[wx] = 0;
|
|
||||||
wcSums[wx] = 0;
|
|
||||||
aSums[wx] = 0;
|
|
||||||
bSums[wx] = 0;
|
|
||||||
gSums[wx] = 0;
|
|
||||||
rSums[wx] = 0;
|
|
||||||
|
|
||||||
int srcX = x + wx - r;
|
|
||||||
|
|
||||||
if (srcX >= 0 && srcX < applyRect.Width) {
|
|
||||||
for (int wy = 0; wy < wlen; ++wy) {
|
|
||||||
int srcY = y + wy - r;
|
|
||||||
// only when in EDIT mode, ignore some pixels depending on preview quality
|
|
||||||
if ((renderMode==RenderMode.EXPORT || rand.NextDouble()<previewQuality) && srcY >= 0 && srcY < applyRect.Height) {
|
|
||||||
int[] colors = bbbSrc.GetColorArrayAt(srcX, srcY);
|
|
||||||
int wp = w[wy];
|
|
||||||
|
|
||||||
waSums[wx] += wp;
|
|
||||||
wp *= colors[0] + (colors[0] >> 7);
|
|
||||||
wcSums[wx] += wp;
|
|
||||||
wp >>= 8;
|
|
||||||
|
|
||||||
aSums[wx] += wp * (long)colors[0];
|
|
||||||
bSums[wx] += wp * (long)colors[3];
|
|
||||||
gSums[wx] += wp * (long)colors[2];
|
|
||||||
rSums[wx] += wp * (long)colors[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int wr = w[wx];
|
|
||||||
waSum += (long)wr * waSums[wx];
|
|
||||||
wcSum += (long)wr * wcSums[wx];
|
|
||||||
aSum += (long)wr * aSums[wx];
|
|
||||||
bSum += (long)wr * bSums[wx];
|
|
||||||
gSum += (long)wr * gSums[wx];
|
|
||||||
rSum += (long)wr * rSums[wx];
|
|
||||||
}
|
|
||||||
|
|
||||||
wcSum >>= 8;
|
|
||||||
|
|
||||||
if (waSum == 0 || wcSum == 0) {
|
|
||||||
SetColorAt(bbbDest, x, y, new int[]{0,0,0,0});
|
|
||||||
} else {
|
|
||||||
int alpha = (int)(aSum / waSum);
|
|
||||||
int blue = (int)(bSum / wcSum);
|
|
||||||
int green = (int)(gSum / wcSum);
|
|
||||||
int red = (int)(rSum / wcSum);
|
|
||||||
|
|
||||||
SetColorAt(bbbDest, x, y, new int[]{alpha, red, green, blue});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bbbDest.DrawTo(graphics, applyRect.Location);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void SetColorAt(BitmapBuffer bbb, int x, int y, int[] colors) {
|
|
||||||
if(parent.Contains(applyRect.Left+x, applyRect.Top+y) ^ Invert) {
|
|
||||||
bbb.SetColorArrayAt(x, y, colors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace Greenshot.Drawing.Filters {
|
||||||
|
|
||||||
public override void Apply(Graphics graphics, Bitmap applyBitmap, Rectangle rect, RenderMode renderMode) {
|
public override void Apply(Graphics graphics, Bitmap applyBitmap, Rectangle rect, RenderMode renderMode) {
|
||||||
magnificationFactor = GetFieldValueAsInt(FieldType.MAGNIFICATION_FACTOR);
|
magnificationFactor = GetFieldValueAsInt(FieldType.MAGNIFICATION_FACTOR);
|
||||||
applyRect = IntersectRectangle(applyBitmap.Size, rect);
|
applyRect = ImageHelper.CreateIntersectRectangle(applyBitmap.Size, rect, Invert);
|
||||||
|
|
||||||
bbbSrc = new BitmapBuffer(applyBitmap, applyRect);
|
bbbSrc = new BitmapBuffer(applyBitmap, applyRect);
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -77,7 +77,7 @@ namespace Greenshot.Drawing.Filters {
|
||||||
|
|
||||||
public override void Apply(Graphics graphics, Bitmap applyBitmap, Rectangle rect, RenderMode renderMode) {
|
public override void Apply(Graphics graphics, Bitmap applyBitmap, Rectangle rect, RenderMode renderMode) {
|
||||||
int pixelSize = GetFieldValueAsInt(FieldType.PIXEL_SIZE);
|
int pixelSize = GetFieldValueAsInt(FieldType.PIXEL_SIZE);
|
||||||
applyRect = IntersectRectangle(applyBitmap.Size, rect);
|
applyRect = ImageHelper.CreateIntersectRectangle(applyBitmap.Size, rect, Invert);
|
||||||
|
|
||||||
Apply(graphics, applyBitmap, applyRect, pixelSize);
|
Apply(graphics, applyBitmap, applyRect, pixelSize);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,12 +34,16 @@ using Greenshot.Plugin.Drawing;
|
||||||
using GreenshotPlugin.Core;
|
using GreenshotPlugin.Core;
|
||||||
using Greenshot.Memento;
|
using Greenshot.Memento;
|
||||||
using IniFile;
|
using IniFile;
|
||||||
|
using Greenshot.Drawing.Filters;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
|
|
||||||
namespace Greenshot.Drawing {
|
namespace Greenshot.Drawing {
|
||||||
public delegate void SurfaceElementEventHandler(object source, DrawableContainerList element);
|
public delegate void SurfaceElementEventHandler(object source, DrawableContainerList element);
|
||||||
public delegate void SurfaceDrawingModeEventHandler(object source, DrawingModes drawingMode);
|
public delegate void SurfaceDrawingModeEventHandler(object source, DrawingModes drawingMode);
|
||||||
|
|
||||||
public enum DrawingModes { None, Rect, Ellipse, Text, Line, Arrow, Crop, Highlight, Obfuscate, Bitmap, Path }
|
public enum DrawingModes { None, Rect, Ellipse, Text, Line, Arrow, Crop, Highlight, Obfuscate, Bitmap, Path }
|
||||||
|
public enum Effects { Shadow, TornEdge }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Description of Surface.
|
/// Description of Surface.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -522,17 +526,29 @@ namespace Greenshot.Drawing {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyBitmapEffect() {
|
public void ApplyBitmapEffect(Effects effect) {
|
||||||
Rectangle cropRectangle = new Rectangle(Point.Empty, Image.Size);
|
Rectangle imageRectangle = new Rectangle(Point.Empty, Image.Size);
|
||||||
Bitmap tmpImage = ((Bitmap)Image).Clone(cropRectangle, Image.PixelFormat);
|
Bitmap newImage = null;
|
||||||
tmpImage.SetResolution(Image.HorizontalResolution, Image.VerticalResolution);
|
|
||||||
|
|
||||||
// Currently only one effect exists, others could follow
|
Point offset = Point.Empty;
|
||||||
ImageHelper.ApplyTornEdge(tmpImage);
|
switch (effect) {
|
||||||
|
case Effects.Shadow:
|
||||||
|
newImage = ImageHelper.CreateShadow((Bitmap)Image, 0.8f, 8, Image.PixelFormat, out offset);
|
||||||
|
break;
|
||||||
|
case Effects.TornEdge:
|
||||||
|
using (Bitmap tmpImage = ImageHelper.CreateTornEdge((Bitmap)Image)) {
|
||||||
|
newImage = ImageHelper.CreateShadow(tmpImage, 0.8f, 8, Image.PixelFormat, out offset);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Make undoable
|
if (newImage != null) {
|
||||||
MakeUndoable(new SurfaceCropMemento(this, cropRectangle), false);
|
// Make sure the elements move according to the offset the effect made the bitmap move
|
||||||
SetImage(tmpImage, false);
|
elements.MoveBy(offset.X, offset.Y);
|
||||||
|
// Make undoable
|
||||||
|
MakeUndoable(new SurfaceCropMemento(this, imageRectangle), false);
|
||||||
|
SetImage(newImage, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool isCropPossible(ref Rectangle cropRectangle) {
|
public bool isCropPossible(ref Rectangle cropRectangle) {
|
||||||
|
@ -757,6 +773,10 @@ namespace Greenshot.Drawing {
|
||||||
Bitmap clone = ImageHelper.CloneImageToBitmap(Image);
|
Bitmap clone = ImageHelper.CloneImageToBitmap(Image);
|
||||||
// otherwise we would have a problem drawing the image to the surface... :(
|
// otherwise we would have a problem drawing the image to the surface... :(
|
||||||
using (Graphics graphics = Graphics.FromImage(clone)) {
|
using (Graphics graphics = Graphics.FromImage(clone)) {
|
||||||
|
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||||
|
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||||
|
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||||
|
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||||
elements.Draw(graphics, (Bitmap)clone, renderMode, new Rectangle(Point.Empty, clone.Size));
|
elements.Draw(graphics, (Bitmap)clone, renderMode, new Rectangle(Point.Empty, clone.Size));
|
||||||
}
|
}
|
||||||
return clone;
|
return clone;
|
||||||
|
@ -792,6 +812,10 @@ namespace Greenshot.Drawing {
|
||||||
}
|
}
|
||||||
// Elements might need the bitmap, so we copy the part we need
|
// Elements might need the bitmap, so we copy the part we need
|
||||||
using (Graphics graphics = Graphics.FromImage(buffer)) {
|
using (Graphics graphics = Graphics.FromImage(buffer)) {
|
||||||
|
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||||
|
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||||
|
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||||
|
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||||
graphics.DrawImage(Image, clipRectangle, clipRectangle, GraphicsUnit.Pixel);
|
graphics.DrawImage(Image, clipRectangle, clipRectangle, GraphicsUnit.Pixel);
|
||||||
graphics.SetClip(targetGraphics);
|
graphics.SetClip(targetGraphics);
|
||||||
elements.Draw(graphics, buffer, RenderMode.EDIT, clipRectangle);
|
elements.Draw(graphics, buffer, RenderMode.EDIT, clipRectangle);
|
||||||
|
|
|
@ -26,6 +26,7 @@ using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using Greenshot.Plugin;
|
using Greenshot.Plugin;
|
||||||
|
using GreenshotPlugin.Core;
|
||||||
|
|
||||||
namespace GreenshotNetworkImportPlugin {
|
namespace GreenshotNetworkImportPlugin {
|
||||||
public class HTTPReceiver {
|
public class HTTPReceiver {
|
||||||
|
@ -127,10 +128,11 @@ namespace GreenshotNetworkImportPlugin {
|
||||||
using (MemoryStream memoryStream = new MemoryStream(imageBytes, 0, imageBytes.Length)) {
|
using (MemoryStream memoryStream = new MemoryStream(imageBytes, 0, imageBytes.Length)) {
|
||||||
// Convert byte[] to Image
|
// Convert byte[] to Image
|
||||||
memoryStream.Write(imageBytes, 0, imageBytes.Length);
|
memoryStream.Write(imageBytes, 0, imageBytes.Length);
|
||||||
Image image = Image.FromStream(memoryStream, true);
|
using (Image image = Bitmap.FromStream(memoryStream, true)) {
|
||||||
ICapture capture = host.GetCapture(image);
|
ICapture capture = host.GetCapture(ImageHelper.CloneImageToBitmap(image));
|
||||||
capture.CaptureDetails.Title = title;
|
capture.CaptureDetails.Title = title;
|
||||||
host.ImportCapture(capture);
|
host.ImportCapture(capture);
|
||||||
|
}
|
||||||
response.StatusCode = (int)HttpStatusCode.OK;
|
response.StatusCode = (int)HttpStatusCode.OK;
|
||||||
byte[] content = Encoding.UTF8.GetBytes("Greenshot-OK");
|
byte[] content = Encoding.UTF8.GetBytes("Greenshot-OK");
|
||||||
response.ContentType = "text";
|
response.ContentType = "text";
|
||||||
|
|
|
@ -59,12 +59,13 @@ namespace GreenshotPlugin.Core {
|
||||||
}
|
}
|
||||||
|
|
||||||
Bitmap bmp = new Bitmap(thumbWidth, thumbHeight);
|
Bitmap bmp = new Bitmap(thumbWidth, thumbHeight);
|
||||||
using (Graphics gr = System.Drawing.Graphics.FromImage(bmp)) {
|
using (Graphics graphics = System.Drawing.Graphics.FromImage(bmp)) {
|
||||||
gr.SmoothingMode = SmoothingMode.HighQuality ;
|
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||||
gr.CompositingQuality = CompositingQuality.HighQuality;
|
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||||
gr.InterpolationMode = InterpolationMode.High;
|
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||||
System.Drawing.Rectangle rectDestination = new System.Drawing.Rectangle(0, 0, thumbWidth, thumbHeight);
|
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||||
gr.DrawImage(image, rectDestination, 0, 0, srcWidth, srcHeight, GraphicsUnit.Pixel);
|
Rectangle rectDestination = new Rectangle(0, 0, thumbWidth, thumbHeight);
|
||||||
|
graphics.DrawImage(image, rectDestination, 0, 0, srcWidth, srcHeight, GraphicsUnit.Pixel);
|
||||||
}
|
}
|
||||||
return bmp;
|
return bmp;
|
||||||
}
|
}
|
||||||
|
@ -301,44 +302,52 @@ namespace GreenshotPlugin.Core {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Make the picture look like it's torn
|
/// Make the picture look like it's torn
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bitmap">Bitmap to modify</param>
|
/// <param name="sourceBitmap">Bitmap to make torn edge off</param>
|
||||||
public static void ApplyTornEdge(Bitmap bitmap) {
|
/// <returns>Changed bitmap</returns>
|
||||||
|
public static Bitmap CreateTornEdge(Bitmap sourceBitmap) {
|
||||||
|
Rectangle cropRectangle = new Rectangle(Point.Empty, sourceBitmap.Size);
|
||||||
|
Bitmap returnImage = sourceBitmap.Clone(cropRectangle, PixelFormat.Format32bppArgb);
|
||||||
|
try {
|
||||||
|
returnImage.SetResolution(sourceBitmap.HorizontalResolution, sourceBitmap.VerticalResolution);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.Warn("An exception occured while setting the resolution.", ex);
|
||||||
|
}
|
||||||
GraphicsPath path = new GraphicsPath();
|
GraphicsPath path = new GraphicsPath();
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
int regionWidth = 14;
|
int regionWidth = 20;
|
||||||
int regionHeight = 14;
|
int regionHeight = 20;
|
||||||
int HorizontalRegions = (int)(bitmap.Width / regionWidth);
|
int HorizontalRegions = (int)(sourceBitmap.Width / regionWidth);
|
||||||
int VerticalRegions = (int)(bitmap.Height / regionHeight);
|
int VerticalRegions = (int)(sourceBitmap.Height / regionHeight);
|
||||||
int distance = 12;
|
int distance = 12;
|
||||||
|
|
||||||
// Start
|
// Start
|
||||||
Point previousEndingPoint = Point.Empty;
|
Point previousEndingPoint = Point.Empty;
|
||||||
Point newEndingPoint = Point.Empty;
|
Point newEndingPoint = Point.Empty;
|
||||||
|
path.AddLine(new Point(sourceBitmap.Width, 0), Point.Empty);
|
||||||
// Top
|
// Top
|
||||||
for (int i = 0; i < HorizontalRegions; i++) {
|
for (int i = 0; i < HorizontalRegions; i++) {
|
||||||
int x = (int)previousEndingPoint.X + regionWidth;
|
int x = (int)previousEndingPoint.X + regionWidth;
|
||||||
int y = random.Next(0, distance);
|
int y = random.Next(1, distance);
|
||||||
newEndingPoint = new Point(x, y);
|
newEndingPoint = new Point(x, y);
|
||||||
path.AddLine(previousEndingPoint, newEndingPoint);
|
path.AddLine(previousEndingPoint, newEndingPoint);
|
||||||
previousEndingPoint = newEndingPoint;
|
previousEndingPoint = newEndingPoint;
|
||||||
}
|
}
|
||||||
// end top
|
// end top
|
||||||
newEndingPoint = new Point(bitmap.Width, 0);
|
newEndingPoint = new Point(sourceBitmap.Width, 0);
|
||||||
path.AddLine(previousEndingPoint, newEndingPoint);
|
path.AddLine(previousEndingPoint, newEndingPoint);
|
||||||
previousEndingPoint = newEndingPoint;
|
previousEndingPoint = newEndingPoint;
|
||||||
path.CloseFigure();
|
path.CloseFigure();
|
||||||
|
|
||||||
// Right
|
// Right
|
||||||
for (int i = 0; i < VerticalRegions; i++) {
|
for (int i = 0; i < VerticalRegions; i++) {
|
||||||
int x = bitmap.Width - random.Next(0, distance);
|
int x = sourceBitmap.Width - random.Next(1, distance);
|
||||||
int y = (int)previousEndingPoint.Y + regionHeight;
|
int y = (int)previousEndingPoint.Y + regionHeight;
|
||||||
newEndingPoint = new Point(x, y);
|
newEndingPoint = new Point(x, y);
|
||||||
path.AddLine(previousEndingPoint, newEndingPoint);
|
path.AddLine(previousEndingPoint, newEndingPoint);
|
||||||
previousEndingPoint = newEndingPoint;
|
previousEndingPoint = newEndingPoint;
|
||||||
}
|
}
|
||||||
// end right
|
// end right
|
||||||
newEndingPoint = new Point(bitmap.Width, bitmap.Height);
|
newEndingPoint = new Point(sourceBitmap.Width, sourceBitmap.Height);
|
||||||
path.AddLine(previousEndingPoint, newEndingPoint);
|
path.AddLine(previousEndingPoint, newEndingPoint);
|
||||||
previousEndingPoint = newEndingPoint;
|
previousEndingPoint = newEndingPoint;
|
||||||
path.CloseFigure();
|
path.CloseFigure();
|
||||||
|
@ -346,20 +355,20 @@ namespace GreenshotPlugin.Core {
|
||||||
// Bottom
|
// Bottom
|
||||||
for (int i = 0; i < HorizontalRegions; i++) {
|
for (int i = 0; i < HorizontalRegions; i++) {
|
||||||
int x = (int)previousEndingPoint.X - regionWidth;
|
int x = (int)previousEndingPoint.X - regionWidth;
|
||||||
int y = bitmap.Height - random.Next(0, distance);
|
int y = sourceBitmap.Height - random.Next(1, distance);
|
||||||
newEndingPoint = new Point(x, y);
|
newEndingPoint = new Point(x, y);
|
||||||
path.AddLine(previousEndingPoint, newEndingPoint);
|
path.AddLine(previousEndingPoint, newEndingPoint);
|
||||||
previousEndingPoint = newEndingPoint;
|
previousEndingPoint = newEndingPoint;
|
||||||
}
|
}
|
||||||
// end Bottom
|
// end Bottom
|
||||||
newEndingPoint = new Point(0, bitmap.Height);
|
newEndingPoint = new Point(0, sourceBitmap.Height);
|
||||||
path.AddLine(previousEndingPoint, newEndingPoint);
|
path.AddLine(previousEndingPoint, newEndingPoint);
|
||||||
previousEndingPoint = newEndingPoint;
|
previousEndingPoint = newEndingPoint;
|
||||||
path.CloseFigure();
|
path.CloseFigure();
|
||||||
|
|
||||||
// Left
|
// Left
|
||||||
for (int i = 0; i < VerticalRegions; i++) {
|
for (int i = 0; i < VerticalRegions; i++) {
|
||||||
int x = random.Next(0, distance);
|
int x = random.Next(1, distance);
|
||||||
int y = (int)previousEndingPoint.Y - regionHeight;
|
int y = (int)previousEndingPoint.Y - regionHeight;
|
||||||
newEndingPoint = new Point(x, y);
|
newEndingPoint = new Point(x, y);
|
||||||
path.AddLine(previousEndingPoint, newEndingPoint);
|
path.AddLine(previousEndingPoint, newEndingPoint);
|
||||||
|
@ -372,17 +381,294 @@ namespace GreenshotPlugin.Core {
|
||||||
path.CloseFigure();
|
path.CloseFigure();
|
||||||
|
|
||||||
// Draw
|
// Draw
|
||||||
using (Graphics graphics = Graphics.FromImage(bitmap)) {
|
using (Graphics graphics = Graphics.FromImage(returnImage)) {
|
||||||
Color fillColor = Color.White;
|
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||||
if (bitmap.PixelFormat == PixelFormat.Format32bppArgb) {
|
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||||
|
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||||
|
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||||
|
|
||||||
|
if (Image.IsAlphaPixelFormat(returnImage.PixelFormat)) {
|
||||||
|
// When using transparency we can't draw with Color.Transparency so we clear
|
||||||
graphics.SetClip(path);
|
graphics.SetClip(path);
|
||||||
graphics.Clear(Color.Transparent);
|
graphics.Clear(Color.Transparent);
|
||||||
} else {
|
} else {
|
||||||
using (Brush brush = new SolidBrush( Color.White)) {
|
using (Brush brush = new SolidBrush(Color.White)) {
|
||||||
graphics.FillPath(brush, path);
|
graphics.FillPath(brush, path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return returnImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper Method for the ApplyBlur
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="amount"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static int[] CreateGaussianBlurRow(int amount) {
|
||||||
|
int size = 1 + (amount * 2);
|
||||||
|
int[] weights = new int[size];
|
||||||
|
|
||||||
|
for (int i = 0; i <= amount; ++i) {
|
||||||
|
// 1 + aa - aa + 2ai - ii
|
||||||
|
weights[i] = 16 * (i + 1);
|
||||||
|
weights[weights.Length - i - 1] = weights[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return weights;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply sourceBitmap with a blur to the targetGraphics
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="targetGraphics">Target to draw to</param>
|
||||||
|
/// <param name="sourceBitmap">Source to blur</param>
|
||||||
|
/// <param name="rect">Area to blur</param>
|
||||||
|
/// <param name="export">Use full quality</param>
|
||||||
|
/// <param name="blurRadius">Radius of the blur</param>
|
||||||
|
/// <param name="previewQuality">Quality, use 1d for normal anything less skipps calculations</param>
|
||||||
|
/// <param name="invert">true if the blur needs to occur outside of the area</param>
|
||||||
|
/// <param name="parentBounds">Rectangle limiting the area when using invert</param>
|
||||||
|
public static void ApplyBlur(Graphics targetGraphics, Bitmap sourceBitmap, Rectangle rect, bool export, int blurRadius, double previewQuality, bool invert, Rectangle parentBounds) {
|
||||||
|
Rectangle applyRect = CreateIntersectRectangle(sourceBitmap.Size, rect, invert);
|
||||||
|
|
||||||
|
if (applyRect.Height <= 0 || applyRect.Width <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// do nothing when nothing can be done!
|
||||||
|
if (blurRadius < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Color nullColor = Color.White;
|
||||||
|
if (sourceBitmap.PixelFormat == PixelFormat.Format32bppArgb) {
|
||||||
|
nullColor = Color.Transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (BitmapBuffer bbbDest = new BitmapBuffer(sourceBitmap, applyRect)) {
|
||||||
|
bbbDest.Lock();
|
||||||
|
using (BitmapBuffer bbbSrc = new BitmapBuffer(sourceBitmap, applyRect)) {
|
||||||
|
bbbSrc.Lock();
|
||||||
|
Random rand = new Random();
|
||||||
|
|
||||||
|
int r = blurRadius;
|
||||||
|
int[] w = CreateGaussianBlurRow(r);
|
||||||
|
int wlen = w.Length;
|
||||||
|
long[] waSums = new long[wlen];
|
||||||
|
long[] wcSums = new long[wlen];
|
||||||
|
long[] aSums = new long[wlen];
|
||||||
|
long[] bSums = new long[wlen];
|
||||||
|
long[] gSums = new long[wlen];
|
||||||
|
long[] rSums = new long[wlen];
|
||||||
|
for (int y = 0; y < applyRect.Height; ++y) {
|
||||||
|
long waSum = 0;
|
||||||
|
long wcSum = 0;
|
||||||
|
long aSum = 0;
|
||||||
|
long bSum = 0;
|
||||||
|
long gSum = 0;
|
||||||
|
long rSum = 0;
|
||||||
|
|
||||||
|
for (int wx = 0; wx < wlen; ++wx) {
|
||||||
|
int srcX = wx - r;
|
||||||
|
waSums[wx] = 0;
|
||||||
|
wcSums[wx] = 0;
|
||||||
|
aSums[wx] = 0;
|
||||||
|
bSums[wx] = 0;
|
||||||
|
gSums[wx] = 0;
|
||||||
|
rSums[wx] = 0;
|
||||||
|
|
||||||
|
if (srcX >= 0 && srcX < bbbDest.Width) {
|
||||||
|
for (int wy = 0; wy < wlen; ++wy) {
|
||||||
|
int srcY = y + wy - r;
|
||||||
|
|
||||||
|
if (srcY >= 0 && srcY < bbbDest.Height) {
|
||||||
|
int[] colors = bbbSrc.GetColorArrayAt(srcX, srcY);
|
||||||
|
int wp = w[wy];
|
||||||
|
|
||||||
|
waSums[wx] += wp;
|
||||||
|
wp *= colors[0] + (colors[0] >> 7);
|
||||||
|
wcSums[wx] += wp;
|
||||||
|
wp >>= 8;
|
||||||
|
|
||||||
|
aSums[wx] += wp * colors[0];
|
||||||
|
bSums[wx] += wp * colors[3];
|
||||||
|
gSums[wx] += wp * colors[2];
|
||||||
|
rSums[wx] += wp * colors[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int wwx = w[wx];
|
||||||
|
waSum += wwx * waSums[wx];
|
||||||
|
wcSum += wwx * wcSums[wx];
|
||||||
|
aSum += wwx * aSums[wx];
|
||||||
|
bSum += wwx * bSums[wx];
|
||||||
|
gSum += wwx * gSums[wx];
|
||||||
|
rSum += wwx * rSums[wx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wcSum >>= 8;
|
||||||
|
|
||||||
|
if (waSum == 0 || wcSum == 0) {
|
||||||
|
if (parentBounds.Contains(applyRect.Left, applyRect.Top + y) ^ invert) {
|
||||||
|
bbbDest.SetColorAt(0, y, nullColor);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int alpha = (int)(aSum / waSum);
|
||||||
|
int blue = (int)(bSum / wcSum);
|
||||||
|
int green = (int)(gSum / wcSum);
|
||||||
|
int red = (int)(rSum / wcSum);
|
||||||
|
if (parentBounds.Contains(applyRect.Left, applyRect.Top + y) ^ invert) {
|
||||||
|
bbbDest.SetColorAt(0, y, Color.FromArgb(alpha, red, green, blue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int x = 1; x < applyRect.Width; ++x) {
|
||||||
|
for (int i = 0; i < wlen - 1; ++i) {
|
||||||
|
waSums[i] = waSums[i + 1];
|
||||||
|
wcSums[i] = wcSums[i + 1];
|
||||||
|
aSums[i] = aSums[i + 1];
|
||||||
|
bSums[i] = bSums[i + 1];
|
||||||
|
gSums[i] = gSums[i + 1];
|
||||||
|
rSums[i] = rSums[i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
waSum = 0;
|
||||||
|
wcSum = 0;
|
||||||
|
aSum = 0;
|
||||||
|
bSum = 0;
|
||||||
|
gSum = 0;
|
||||||
|
rSum = 0;
|
||||||
|
|
||||||
|
int wx;
|
||||||
|
for (wx = 0; wx < wlen - 1; ++wx) {
|
||||||
|
long wwx = (long)w[wx];
|
||||||
|
waSum += wwx * waSums[wx];
|
||||||
|
wcSum += wwx * wcSums[wx];
|
||||||
|
aSum += wwx * aSums[wx];
|
||||||
|
bSum += wwx * bSums[wx];
|
||||||
|
gSum += wwx * gSums[wx];
|
||||||
|
rSum += wwx * rSums[wx];
|
||||||
|
}
|
||||||
|
|
||||||
|
wx = wlen - 1;
|
||||||
|
|
||||||
|
waSums[wx] = 0;
|
||||||
|
wcSums[wx] = 0;
|
||||||
|
aSums[wx] = 0;
|
||||||
|
bSums[wx] = 0;
|
||||||
|
gSums[wx] = 0;
|
||||||
|
rSums[wx] = 0;
|
||||||
|
|
||||||
|
int srcX = x + wx - r;
|
||||||
|
|
||||||
|
if (srcX >= 0 && srcX < applyRect.Width) {
|
||||||
|
for (int wy = 0; wy < wlen; ++wy) {
|
||||||
|
int srcY = y + wy - r;
|
||||||
|
// only when in EDIT mode, ignore some pixels depending on preview quality
|
||||||
|
if ((export || rand.NextDouble() < previewQuality) && srcY >= 0 && srcY < applyRect.Height) {
|
||||||
|
int[] colors = bbbSrc.GetColorArrayAt(srcX, srcY);
|
||||||
|
int wp = w[wy];
|
||||||
|
|
||||||
|
waSums[wx] += wp;
|
||||||
|
wp *= colors[0] + (colors[0] >> 7);
|
||||||
|
wcSums[wx] += wp;
|
||||||
|
wp >>= 8;
|
||||||
|
|
||||||
|
aSums[wx] += wp * (long)colors[0];
|
||||||
|
bSums[wx] += wp * (long)colors[3];
|
||||||
|
gSums[wx] += wp * (long)colors[2];
|
||||||
|
rSums[wx] += wp * (long)colors[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int wr = w[wx];
|
||||||
|
waSum += (long)wr * waSums[wx];
|
||||||
|
wcSum += (long)wr * wcSums[wx];
|
||||||
|
aSum += (long)wr * aSums[wx];
|
||||||
|
bSum += (long)wr * bSums[wx];
|
||||||
|
gSum += (long)wr * gSums[wx];
|
||||||
|
rSum += (long)wr * rSums[wx];
|
||||||
|
}
|
||||||
|
|
||||||
|
wcSum >>= 8;
|
||||||
|
|
||||||
|
if (waSum == 0 || wcSum == 0) {
|
||||||
|
if (parentBounds.Contains(applyRect.Left + x, applyRect.Top + y) ^ invert) {
|
||||||
|
bbbDest.SetColorAt(x, y, nullColor);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int alpha = (int)(aSum / waSum);
|
||||||
|
int blue = (int)(bSum / wcSum);
|
||||||
|
int green = (int)(gSum / wcSum);
|
||||||
|
int red = (int)(rSum / wcSum);
|
||||||
|
if (parentBounds.Contains(applyRect.Left + x, applyRect.Top + y) ^ invert) {
|
||||||
|
bbbDest.SetColorAt(x, y, Color.FromArgb(alpha, red, green, blue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bbbDest.DrawTo(targetGraphics, applyRect.Location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method fixes the problem that we can't apply a filter outside the target bitmap,
|
||||||
|
* therefor the filtered-bitmap will be shifted if we try to draw it outside the target bitmap.
|
||||||
|
* It will also account for the Invert flag.
|
||||||
|
*/
|
||||||
|
public static Rectangle CreateIntersectRectangle(Size applySize, Rectangle rect, bool invert) {
|
||||||
|
Rectangle myRect;
|
||||||
|
if (invert) {
|
||||||
|
myRect = new Rectangle(0, 0, applySize.Width, applySize.Height);
|
||||||
|
} else {
|
||||||
|
Rectangle applyRect = new Rectangle(0, 0, applySize.Width, applySize.Height);
|
||||||
|
myRect = new Rectangle(rect.X, rect.Y, rect.Width, rect.Height);
|
||||||
|
myRect.Intersect(applyRect);
|
||||||
|
}
|
||||||
|
return myRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new bitmap where the sourceBitmap has a shadow
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceBitmap">Bitmap to make a shadow on</param>
|
||||||
|
/// <param name="darkness">How dark is the shadow</param>
|
||||||
|
/// <param name="shadowSize">Size of the shadow</param>
|
||||||
|
/// <param name="targetPixelformat">What pixel format must the returning bitmap have</param>
|
||||||
|
/// <param name="offset">How many pixels is the original image moved?</param>
|
||||||
|
/// <returns>Bitmap with the shadow, is bigger than the sourceBitmap!!</returns>
|
||||||
|
public static Bitmap CreateShadow(Bitmap sourceBitmap, float darkness, int shadowSize, PixelFormat targetPixelformat, out Point offset) {
|
||||||
|
Bitmap newImage = new Bitmap(sourceBitmap.Width + (shadowSize * 2), sourceBitmap.Height + (shadowSize * 2), targetPixelformat);
|
||||||
|
|
||||||
|
using (Graphics graphics = Graphics.FromImage(newImage)) {
|
||||||
|
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||||
|
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||||
|
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||||
|
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||||
|
|
||||||
|
if (Image.IsAlphaPixelFormat(targetPixelformat)) {
|
||||||
|
graphics.Clear(Color.Transparent);
|
||||||
|
} else {
|
||||||
|
graphics.Clear(Color.White);
|
||||||
|
}
|
||||||
|
ImageAttributes ia = new ImageAttributes();
|
||||||
|
ColorMatrix cm = new ColorMatrix();
|
||||||
|
cm.Matrix00 = 0;
|
||||||
|
cm.Matrix11 = 0;
|
||||||
|
cm.Matrix22 = 0;
|
||||||
|
cm.Matrix33 = darkness;
|
||||||
|
ia.SetColorMatrix(cm);
|
||||||
|
// Draw "shadow" offsetted
|
||||||
|
graphics.DrawImage(sourceBitmap, new Rectangle(new Point(shadowSize, shadowSize), sourceBitmap.Size), 0, 0, sourceBitmap.Width, sourceBitmap.Height, GraphicsUnit.Pixel, ia);
|
||||||
|
// blur "shadow", apply to whole new image
|
||||||
|
Rectangle applyRectangle = new Rectangle(Point.Empty, newImage.Size);
|
||||||
|
ApplyBlur(graphics, newImage, applyRectangle, true, shadowSize, 1d, false, applyRectangle);
|
||||||
|
// draw original
|
||||||
|
offset = new Point(shadowSize - 2, shadowSize - 2);
|
||||||
|
graphics.DrawImage(sourceBitmap, offset);
|
||||||
|
}
|
||||||
|
return newImage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue