- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
在 GDI 中向图像添加阴影的有效方法是什么?
现在我从我的形象开始:
我使用 ImageAttributes 和 ColorMatrix 将图像的 alpha 蒙版绘制到新图像上:
colorMatrix = (
( 0, 0, 0, 0, 0),
( 0, 0, 0, 0, 0),
( 0, 0, 0, 0, 0),
(-1, -1, -1, 1, 0),
( 1, 1, 1, 0, 1)
);
然后我应用高斯模糊卷积核,并稍微偏移它:
然后我将我的原始图像画回顶部:
问题是它太慢了,生成带有阴影的图像大约需要 170 毫秒,而没有阴影的情况下需要 2 毫秒(慢 70 倍):
171,332 µs
2,457us
当用户(例如我)滚动浏览项目列表时,额外的 169 毫秒延迟非常很明显。
您可以忽略下面的代码,它不会向问题或答案添加任何内容:
class function TImageEffects.GenerateDropShadow(image: TGPImage;
const radius: Single; const OffsetX, OffsetY: Single; const Opacity: Single): TGPBitmap;
var
width, height: Integer;
alphaMask: TGPBitmap;
shadow: TGPBitmap;
graphics: TGPGraphics;
imageAttributes: TGPImageAttributes;
cm: TColorMatrix;
begin
{
We generate a drop shadow by first getting the alpha mask. This will be a black
sillouette on a transparent background. We then blur the black "shadow" by the amounts
given.
We then draw the original image on top of it's own shadow.
}
{
http://msdn.microsoft.com/en-us/library/aa511280.aspx
Windows Vista User Experience -> Guidelines -> Aesthetics -> Icons
Basic Flat Icon Shadow Ranges
Flat icons
Flat icons are generally used for file icons and flat real-world objects,
such as a document or a piece of paper.
Flat icon lighting comes from the upper-left at 130 degrees.
Smaller icons (for example, 16x16 and 32x32) are simplified for readability.
However, if they contain a reflection within the icon (often simplified),
they may have a tight drop shadow. The drop shadow ranges in opacity from
30-50 percent.
Layer effects can be used for flat icons, but should be compared with other
flat icons. The shadows for objects will vary somewhat, according to what
looks best and is most consistent within the size set and with the other
icons in Windows Vista. On some occasions, it may even be necessary to
modify the shadows. This will especially be true when objects are laid over
others.
A subtle range of colors may be used to achieve desired outcome. Shadows help
objects sit in space. Color impacts the perceived weight of the shadow, and
may distort the image if it is too heavy.
Blend mode: Multiply
Opacity: 22% to 50% - depends on color of the item.
Angle: 130 to 120, use global light
Distance: 3 (256 thru 48x), Distance = 1 (32x, 24x)
Spread: 0
Size: 7 (256x thru 48x), Spread = 2 (32x, 24x)
}
width := image.GetWidth;
height := image.GetHeight;
//Get bitmap to hold final composited image and shadow
Result := TGPBitmap.Create(width, height, PixelFormat32bppARGB);
//Use ColorMatrix methods to "draw" the alpha image.
alphaMask := TImageEffects.GetAlphaMask(image);
try
//Blur the black and white shadow image
// shadow := TImageEffects.BoxBlur(alphaMask, radius);
shadow := TImageEffects.GaussianBlur(alphaMask, radius); //because Gaussian Blur is linearly-separable into two 1d kernels, it's actually faster than the box blur
finally
alphaMask.Free;
end;
//Draw
graphics := TGPGraphics.Create(Result);
try
//Draw the "shadow", using the passed in opacity value.
{
Color transformations are of the form
c = (r, g, b, a)
c' = (r, g, b, a)
c' = c*M
= (r, g, b, a, 1) * (0 0 0 0 0) //r
(0 0 0 0 0) //g
(0 0 0 0 0) //b
(1 1 1 1 0) //a
(0 0 0 0 1) //1
}
imageAttributes := TGPImageAttributes.Create;
{ cm := (
( 1, 0, 0, 0, 0),
( 0, 1, 0, 0, 0),
( 0, 0, 1, 0, 0),
( 0, 0, 0, 0.5, 0),
( 0, 0, 0, 0, 1)
);}
cm[0, 0] := 1; cm[0, 1] := 0; cm[0, 2] := 0; cm[0, 3] := 0; cm[0, 4] := 0;
cm[1, 0] := 0; cm[1, 1] := 1; cm[1, 2] := 0; cm[1, 3] := 0; cm[1, 4] := 0;
cm[2, 0] := 0; cm[2, 1] := 0; cm[2, 2] := 1; cm[2, 3] := 0; cm[2, 4] := 0;
cm[3, 0] := 0; cm[3, 1] := 0; cm[3, 2] := 0; cm[3, 3] := Opacity; cm[3, 4] := 0;
cm[4, 0] := 0; cm[4, 1] := 0; cm[4, 2] := 0; cm[4, 3] := 0; cm[4, 4] := 1;
imageAttributes.SetColorMatrix(
cm,
ColorMatrixFlagsDefault,
ColorAdjustTypeBitmap);
try
graphics.DrawImage(shadow,
MakeRectF(OffsetX, OffsetY, width, height), //destination rectangle
0, 0, //source (x,y)
width, height, //source width, height
UnitPixel,
ImageAttributes);
//Draw original image over-top of it's shadow
graphics.DrawImage(image, 0, 0);
finally
imageAttributes.Free;
end;
finally
graphics.Free;
end;
end;
其中使用函数获取灰度alpha掩码:
class function TImageEffects.GetAlphaMask(image: TGPImage): TGPBitmap;
var
imageAttributes: TGPImageAttributes;
cm: TColorMatrix;
graphics: TGPGraphics;
Width, Height: UINT;
begin
{
Color transformations are of the form
c = (r, g, b, a)
c' = (r, g, b, a)
c' = c*M
= (r, g, b, a, 1) * (0 0 0 0 0)
(0 0 0 0 0)
(0 0 0 0 0)
(1 1 1 1 0)
(0 0 0 0 1)
}
imageAttributes := TGPImageAttributes.Create;
{ cm := (
( 0, 0, 0, 0, 0),
( 0, 0, 0, 0, 0),
( 0, 0, 0, 0, 0),
(-1, -1, -1, 1, 0),
( 1, 1, 1, 0, 1)
);}
cm[0, 0] := 0; cm[0, 1] := 0; cm[0, 2] := 0; cm[0, 3] := 0; cm[0, 4] := 0;
cm[1, 0] := 0; cm[1, 1] := 0; cm[1, 2] := 0; cm[1, 3] := 0; cm[1, 4] := 0;
cm[2, 0] := 0; cm[2, 1] := 0; cm[2, 2] := 0; cm[2, 3] := 0; cm[2, 4] := 0;
cm[3, 0] := -1; cm[3, 1] := -1; cm[3, 2] := -1; cm[3, 3] := 1; cm[3, 4] := 0;
cm[4, 0] := 1; cm[4, 1] := 1; cm[4, 2] := 1; cm[4, 3] := 0; cm[4, 4] := 1;
imageAttributes.SetColorMatrix(
cm,
ColorMatrixFlagsDefault,
ColorAdjustTypeBitmap);
width := image.GetWidth;
height := image.GetHeight;
Result := TGPBitmap.Create(Integer(width), Integer(height));
graphics := TGPGraphics.Create(Result);
try
graphics.DrawImage(
image,
MakeRect(0, 0, width, height), //destination rectangle
0, 0, //source (x,y)
width, height,
UnitPixel,
ImageAttributes);
finally
graphics.Free;
end;
end;
核心是高斯模糊:
class function TImageEffects.GaussianBlur(const bitmap: TGPBitmap;
radius: Single): TGPBitmap;
var
width, height: Integer;
tempBitmap: TGPBitmap;
bdSource: TBitmapData;
bdTemp: TBitmapData;
bdDest: TBitmapData;
pSrc: PARGBArray;
pTemp: PARGBArray;
pDest: PARGBArray;
stride: Integer;
kernel: TKernel;
begin
// kernel := MakeGaussianKernel2d(radius);
kernel := MakeGaussianKernel1d(radius);
try
// Result := ConvolveBitmap(bitmap, kernel); brute 2d kernel
width := bitmap.GetWidth;
height := bitmap.GetHeight;
// GDI+ still lies to us - the return format is BGR, NOT RGB.
bitmap.LockBits(MakeRect(0, 0, width, height),
ImageLockModeRead,
PixelFormat32bppPARGB, bdSource);
//intermediate bitmap
tempBitmap := TGPBitmap.Create(width, height, PixelFormat32bppPARGB);
tempBitmap.LockBits(MakeRect(0, 0, width, height),
ImageLockModeWrite,
PixelFormat32bppPARGB, bdTemp);
//target bitmap
Result := TGPBitmap.Create(width, height, PixelFormat32bppARGB);
Result.LockBits(MakeRect(0, 0, width, height),
ImageLockModeWrite,
PixelFormat32bppPARGB, bdDest);
pSrc := PARGBArray(bdSource.Scan0);
pTemp := PARGBArray(bdTemp.Scan0);
pDest := PARGBArray(bdDest.Scan0);
stride := bdSource.Stride;
ConvolveAndTranspose(kernel, pSrc^, pTemp^, width, height, stride, True, EdgeActionClampEdges);
ConvolveAndTranspose(kernel, pTemp^, pDest^, height, width, stride, True, EdgeActionClampEdges);
//Unlock source
bitmap.UnlockBits(bdSource);
tempBitmap.UnlockBits(bdTemp);
Result.UnlockBits(bdDest);
//get rid of temp
tempBitmap.Free;
finally
kernel.Free;
end;
end;
这需要一维内核:
class function TImageEffects.MakeGaussianKernel1d(radius: Single): TKernel;
var
r: Integer;
rows: Integer;
matrix: TSingleDynArray;
sigma: Single;
sigma22: Single;
sigmaPi2: Single;
sqrtSigmaPi2: Single;
radius2: Single;
total: Single;
index: Integer;
row: Integer;
distance: Single;
i: Integer;
begin
r := Ceil(radius);
rows := r*2+1;
SetLength(matrix, rows);
sigma := radius/3.0;
sigma22 := 2*sigma*sigma;
sigmaPi2 := 2*pi*sigma;
sqrtSigmaPi2 := Sqrt(sigmaPi2);
radius2 := radius*radius;
total := 0;
Index := 0;
for row := -r to r do
begin
distance := row*row;
if (distance > radius2) then
matrix[index] := 0
else
begin
matrix[index] := Exp((-distance)/sigma22) / sqrtSigmaPi2;
total := total + matrix[index];
Inc(index);
end;
end;
//Normalize the values
for i := 0 to rows-1 do
matrix[i] := matrix[i] / total;
Result := TKernel.Create(rows, 1, matrix);
end;
然后高斯函数的神奇之处在于它可以分为两个一维卷积:
class procedure TImageEffects.convolveAndTranspose(kernel: TKernel;
const inPixels: array of ARGB; var outPixels: array of ARGB; width,
height, stride: Integer; alpha: Boolean; edgeAction: TEdgeAction);
var
index: Integer;
matrix: TSingleDynArray;
rows: Integer; //number of rows in the kernel
cols: Integer; //number of columns in the kernel
rows2: Integer; //half row count
cols2: Integer; //half column count
x, y: Integer; //
r, g, b, a: Single; //summed red, green, blue, alpha values
row, col: Integer;
ix, iy, ioffset: Integer;
moffset: Integer;
f: Single;
rgb: ARGB;
ir, ig, ib, ia: Integer;
function ClampPixel(value: Single): Integer;
begin
Result := Trunc(value+0.5);
if Result < 0 then
Result := 0
else if Result > 255 then
Result := 255;
end;
begin
matrix := kernel.KernelData;
cols := kernel.Width;
cols2 := cols div 2;
for y := 0 to height-1 do
begin
index := y;
ioffset := y*width;
for x := 0 to width-1 do
begin
r := 0;
g := 0;
b := 0;
a := 0;
moffset := cols2;
for col := -cols2 to cols2 do
begin
f := matrix[moffset+col];
if (f <> 0) then
begin
ix := x+col;
if ( ix < 0 ) then
begin
if ( edgeAction = EdgeActionClampEdges ) then
ix := 0
else if ( edgeAction = EdgeActionWrapEdges ) then
ix := (x+width) mod width;
end
else if ( ix >= width) then
begin
if ( edgeAction = EdgeActionClampEdges ) then
ix := width-1
else if ( edgeAction = EdgeActionWrapEdges ) then
ix := (x+width) mod width;
end;
rgb := inPixels[ioffset+ix];
a := a + f * ((rgb shr 24) and $FF);
r := r + f * ((rgb shr 16) and $FF);
g := g + f * ((rgb shr 8) and $FF);
b := b + f * ((rgb ) and $FF);
end;
end;
if alpha then
ia := ClampPixel(a)
else
ia := $FF;
ir := ClampPixel(r);
ig := ClampPixel(g);
ib := ClampPixel(b);
outPixels[index] := MakeARGB(ia, ir, ig, ib);
Inc(index, height);
end;
end;
end;
使用示例,在我的 256x256 源图像上:
image := TImageEffects.GenerateDropShadow(localImage, 14, 2.12132, 2.12132, 1.0);
分析显示 88.62% 的时间花费在以下行中:
a := a + f * ((rgb shr 24) and $FF);
r := r + f * ((rgb shr 16) and $FF);
g := g + f * ((rgb shr 8) and $FF);
b := b + f * ((rgb ) and $FF);
这是每像素 alpha 混合。
这让我认为,在所有 Windows 和 OSX 实时向窗口应用阴影之后,有一种应用模糊效果的软阴影的更好方法。
最佳答案
该算法来自此博客条目:http://blog.ivank.net/fastest-gaussian-blur.html .当然,它正在实现最新和最快的版本。 :-)
它是直接从我的工作代码中复制的,因此外部假设可能反射(reflect)了这一点。该函数返回一个更大的位图以适应大小的增加。当然,在您的代码中,您需要相应地处理这个问题。它采用 32 位 alpha 图片,但可以轻松修改以仅处理 24 位(CHANNELS
常量和 PixelFormat
值)。
public static class DropShadow {
const int CHANNELS = 4;
public static Bitmap CreateShadow(Bitmap bitmap, int radius, float opacity) {
// Alpha mask with opacity
var matrix = new ColorMatrix(new float[][] {
new float[] { 0F, 0F, 0F, 0F, 0F },
new float[] { 0F, 0F, 0F, 0F, 0F },
new float[] { 0F, 0F, 0F, 0F, 0F },
new float[] { -1F, -1F, -1F, opacity, 0F },
new float[] { 1F, 1F, 1F, 0F, 1F }
});
var imageAttributes = new ImageAttributes();
imageAttributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
var shadow = new Bitmap(bitmap.Width + 4 * radius, bitmap.Height + 4 * radius);
using (var graphics = Graphics.FromImage(shadow))
graphics.DrawImage(bitmap, new Rectangle(2 * radius, 2 * radius, bitmap.Width, bitmap.Height), 0, 0, bitmap.Width, bitmap.Height, GraphicsUnit.Pixel, imageAttributes);
// Gaussian blur
var clone = shadow.Clone() as Bitmap;
var shadowData = shadow.LockBits(new Rectangle(0, 0, shadow.Width, shadow.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
var cloneData = clone.LockBits(new Rectangle(0, 0, clone.Width, clone.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
var boxes = DetermineBoxes(radius, 3);
BoxBlur(shadowData, cloneData, shadow.Width, shadow.Height, (boxes[0] - 1) / 2);
BoxBlur(shadowData, cloneData, shadow.Width, shadow.Height, (boxes[1] - 1) / 2);
BoxBlur(shadowData, cloneData, shadow.Width, shadow.Height, (boxes[2] - 1) / 2);
shadow.UnlockBits(shadowData);
clone.UnlockBits(cloneData);
return shadow;
}
private static unsafe void BoxBlur(BitmapData data1, BitmapData data2, int width, int height, int radius) {
byte* p1 = (byte*)(void*)data1.Scan0;
byte* p2 = (byte*)(void*)data2.Scan0;
int radius2 = 2 * radius + 1;
int[] sum = new int[CHANNELS];
int[] FirstValue = new int[CHANNELS];
int[] LastValue = new int[CHANNELS];
// Horizontal
int stride = data1.Stride;
for (var row = 0; row < height; row++) {
int start = row * stride;
int left = start;
int right = start + radius * CHANNELS;
for (int channel = 0; channel < CHANNELS; channel++) {
FirstValue[channel] = p1[start + channel];
LastValue[channel] = p1[start + (width - 1) * CHANNELS + channel];
sum[channel] = (radius + 1) * FirstValue[channel];
}
for (var column = 0; column < radius; column++)
for (int channel = 0; channel < CHANNELS; channel++)
sum[channel] += p1[start + column * CHANNELS + channel];
for (var column = 0; column <= radius; column++, right += CHANNELS, start += CHANNELS)
for (int channel = 0; channel < CHANNELS; channel++) {
sum[channel] += p1[right + channel] - FirstValue[channel];
p2[start + channel] = (byte)(sum[channel] / radius2);
}
for (var column = radius + 1; column < width - radius; column++, left += CHANNELS, right += CHANNELS, start += CHANNELS)
for (int channel = 0; channel < CHANNELS; channel++) {
sum[channel] += p1[right + channel] - p1[left + channel];
p2[start + channel] = (byte)(sum[channel] / radius2);
}
for (var column = width - radius; column < width; column++, left += CHANNELS, start += CHANNELS)
for (int channel = 0; channel < CHANNELS; channel++) {
sum[channel] += LastValue[channel] - p1[left + channel];
p2[start + channel] = (byte)(sum[channel] / radius2);
}
}
// Vertical
stride = data2.Stride;
for (int column = 0; column < width; column++) {
int start = column * CHANNELS;
int top = start;
int bottom = start + radius * stride;
for (int channel = 0; channel < CHANNELS; channel++) {
FirstValue[channel] = p2[start + channel];
LastValue[channel] = p2[start + (height - 1) * stride + channel];
sum[channel] = (radius + 1) * FirstValue[channel];
}
for (int row = 0; row < radius; row++)
for (int channel = 0; channel < CHANNELS; channel++)
sum[channel] += p2[start + row * stride + channel];
for (int row = 0; row <= radius; row++, bottom += stride, start += stride)
for (int channel = 0; channel < CHANNELS; channel++) {
sum[channel] += p2[bottom + channel] - FirstValue[channel];
p1[start + channel] = (byte)(sum[channel] / radius2);
}
for (int row = radius + 1; row < height - radius; row++, top += stride, bottom += stride, start += stride)
for (int channel = 0; channel < CHANNELS; channel++) {
sum[channel] += p2[bottom + channel] - p2[top + channel];
p1[start + channel] = (byte)(sum[channel] / radius2);
}
for (int row = height - radius; row < height; row++, top += stride, start += stride)
for (int channel = 0; channel < CHANNELS; channel++) {
sum[channel] += LastValue[channel] - p2[top + channel];
p1[start + channel] = (byte)(sum[channel] / radius2);
}
}
}
private static int[] DetermineBoxes(double Sigma, int BoxCount) {
double IdealWidth = Math.Sqrt((12 * Sigma * Sigma / BoxCount) + 1);
int Lower = (int)Math.Floor(IdealWidth);
if (Lower % 2 == 0)
Lower--;
int Upper = Lower + 2;
double MedianWidth = (12 * Sigma * Sigma - BoxCount * Lower * Lower - 4 * BoxCount * Lower - 3 * BoxCount) / (-4 * Lower - 4);
int Median = (int)Math.Round(MedianWidth);
int[] BoxSizes = new int[BoxCount];
for (int i = 0; i < BoxCount; i++)
BoxSizes[i] = (i < Median) ? Lower : Upper;
return BoxSizes;
}
}
我认为将其转换为 Delphi 一定很简单。
附录:根据那个博客的评论,如果你有一个整数半径和三个盒子,你实际上可以忘记 DetermineBoxes()
并使用:
BoxBlur(shadowData, cloneData, shadow.Width, shadow.Height, radius - 1);
BoxBlur(shadowData, cloneData, shadow.Width, shadow.Height, radius - 1);
BoxBlur(shadowData, cloneData, shadow.Width, shadow.Height, radius);
与位图本身相比,它的执行时间可以忽略不计,但仍然...
关于GDI+中的快速投影算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7364026/
如何更改循环中变量的名称?比如 number1 、 number2 、 number3 、 number4 ? var array = [2,4,6,8] func ap ( number1: Int
我想设置 View 的背景颜色并在一定延迟后将其更改为另一种颜色。这是我的尝试方式: print("setting color 1") self.view.backgroundColor = UICo
我在使用 express-session 时遇到问题。 session 数据不会在请求之间持续存在。 正如您在下面的代码中看到的那样,/join 路由设置了一些 session 属性,但是当 /sur
我试图从叶渲染器获得一个非常简单的结果,用于快速 Steam 的 for 循环。 我正在上传叶文件 HTML,因为它不接受此处格式正确的代码 - 下面的pizza.swift代码- import
你们中有人有什么好的链接可以与我分享吗?我正在寻找一个 FAST 程序员编辑器,它可以非常快速地打开包含超过 100, 000 行代码的文件?我目前正在使用记事本自动取款机,打开一个 29000 行长
我现在正在处理眼动追踪数据,因此拥有一个巨大的数据集(想想数百万行),因此希望有一种快速的方法来完成此任务。这是它的简化版本。 数据告诉您眼睛在每个时间点正在查看的位置以及我们正在查看的每个文件。 X
我是新手,想为计时器或其他设备选择提示音。 如何打开此列表,以选择其中一种声音? Alert sound list 最佳答案 您将无法在应用中使用系统声音。 但是,您可以包括自己的声音文件,并将其显示
我编写了以下代码来构建具有顺序字符串的数组。 它的工作方式与我预期的一样,但我希望它能更快地运行。有没有更有效的方法在PowerShell中产生我想要的结果? 我是PowerShell的新手,非常感谢
我有一个包含一些非唯一行的矩阵,例如: x 尝试 y <- rle(apply(x, 1, paste, collapse = " ")) # y$lengths is the vector con
我的函数“keyboardWillShown”有问题。所以我想要的是菜单打开时,菜单正好出现在键盘上方。它可以在Iphone 8 plus,8、7、6上完美运行。但是,当我在模拟器上运行Iphone
我正在尝试通过Swift 5中的HTTP get方法从API提取数据。它在启动时成功加载了数据,但是当我刷新页面时,它说“索引超出范围”,这是因为数据是不再会在我的日志中读取,因此索引中没有任何内容。
我想做什么: 从我的数据库中获取时间戳并将其转换为用户的时区。 我的代码: let tryItNow = "\(model.timestampName)" let format = D
给定字体名称和字体大小,如何查找字符串的宽度(CGFloat)? (目标是将UIView的宽度设置为足以容纳字符串的宽度。) 我有两个字符串:一个重复“1”,重复36次,另一个重复“M”,重复36次。
我正在尝试解析此JSON ["Items": ( { AccountBalance = 0; AlphabetType = 3; Description = "\U0631\U
我在UINavigationBar内放置了一个UILabel。 我想根据navigationBar的高度增加该标签的字体大小。当navigationBar很大时,我希望字体大小更大;当滚动并缩小nav
我想将用户输入限制为仅有效数字并使用以下内容: func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, rep
目前我有一个包含超过 100.000 张图像的数据库,它们大小不一或类似,但我想为我的公司制作以下内容: 我插入/上传一张图片,系统返回最有可能相同的图片。我不知道使用什么算法,但它需要快速。我可以预
在我的 swift 项目中,我有一个按钮,我想在标签上打印按下该按钮的时间。 如何解决这个问题? 最佳答案 添加到DHEERAJ的答案中,您只需在func press(sender: UIButton
我必须发表评论,尝试在解析中导入数组。然而,有一个问题。 当我尝试从 Parse 加载数组时,我的输出是 ("Blah","Blah","Blah")这是一个元组...而不是一个数组 TT... 如何
我的应用程序有一个名为 MyDevice 的类,我用它来与硬件通信。该硬件是可选的,实例变量也是可选的: var theDevice:MyDevice = nil 然后,在应用程序中,我必须初始化设备
我是一名优秀的程序员,十分优秀!