- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
所以这里有一些背景。我正在开发这款名为 ShiftOS 的游戏,该游戏发生在一个操作系统中,该操作系统最初是 80 年代操作系统的基本运行,功能不多。
我正在尝试添加一种机制,用户必须从二进制(2 色)颜色深度开始,并且只能在屏幕上显示黑色和白色。然后他们必须将颜色深度从 1 位升级到 2 位,再到 4 位,一直到 24 位。这是一个非常巧妙的机制,但在实践中似乎极其困难。
当然,当时的旧系统至少尝试让图像看起来不错,但当然它们受到工程师提供的调色板的限制,因此他们不得不抖动图像以按照一种方式排列像素看起来图像使用了更多颜色,而实际上它只能使用 2 种颜色。
所以我查找了一些好的抖动算法并开始学习 Floyd-Steinberg 算法并很快将其移植到 C#
和 System.Drawing
。
这是我使用的代码。
var bmp = new Bitmap(source.Width, source.Height);
var sourceBmp = (Bitmap)source;
int error = 0;
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
Color c = sourceBmp.GetPixel(x, y);
int gray = ((c.R + c.G + c.B) / 3);
if (gray >= 127)
{
error = gray - 255;
bmp.SetPixel(x, y, Color.White);
}
else
{
error = gray;
bmp.SetPixel(x, y, Color.Black);
}
/*
* Pixel error diffusion map: Floyd-Steinberg. Thanks to Wikipedia.
*
* pixel[x + 1][y ] := pixel[x + 1][y ] + quant_error * 7 / 16
* pixel[x - 1][y + 1] := pixel[x - 1][y + 1] + quant_error * 3 / 16
* pixel[x ][y + 1] := pixel[x ][y + 1] + quant_error * 5 / 16
* pixel[x + 1][y + 1] := pixel[x + 1][y + 1] + quant_error * 1 / 16
*/
if(x - 1 >= 0 && y + 1 != bmp.Height)
{
var bottomRightColor = sourceBmp.GetPixel(x - 1, y + 1);
int bottomRightGray = ((bottomRightColor.R + bottomRightColor.G + bottomRightColor.B) / 3) + ((error * 3) / 16);
if (bottomRightGray < 0)
bottomRightGray = 0;
if (bottomRightGray > 255)
bottomRightGray = 255;
sourceBmp.SetPixel(x - 1, y + 1, Color.FromArgb(bottomRightGray, bottomRightGray, bottomRightGray));
}
if (x + 1 != sourceBmp.Width)
{
var rightColor = sourceBmp.GetPixel(x + 1, y);
int rightGray = ((rightColor.R + rightColor.G + rightColor.B) / 3) + ((error * 7) / 16);
if (rightGray < 0)
rightGray = 0;
if (rightGray > 255)
rightGray = 255;
sourceBmp.SetPixel(x + 1, y, Color.FromArgb(rightGray, rightGray, rightGray));
}
if (x + 1 != sourceBmp.Width && y + 1 != sourceBmp.Height)
{
var bottomRightColor = sourceBmp.GetPixel(x + 1, y + 1);
int bottomRightGray = ((bottomRightColor.R + bottomRightColor.G + bottomRightColor.B) / 3) + ((error) / 16);
if (bottomRightGray < 0)
bottomRightGray = 0;
if (bottomRightGray > 255)
bottomRightGray = 255;
sourceBmp.SetPixel(x + 1, y + 1, Color.FromArgb(bottomRightGray, bottomRightGray, bottomRightGray));
}
if (y + 1 != sourceBmp.Height)
{
var bottomColor = sourceBmp.GetPixel(x, y + 1);
int bottomGray = ((bottomColor.R + bottomColor.G + bottomColor.B) / 3) + ((error * 5) / 16);
if (bottomGray < 0)
bottomGray = 0;
if (bottomGray > 255)
bottomGray = 255;
sourceBmp.SetPixel(x, y + 1, Color.FromArgb(bottomGray, bottomGray, bottomGray));
}
}
}
请注意,source
是通过参数传递给函数的 Image
。
这段代码工作得很好,但问题是,抖动发生在一个单独的线程上以最大限度地减少游戏中的减速/滞后,并且在抖动发生时,操作系统的常规 24 位颜色/图像显示。如果抖动不花费这么长时间,这会很好。
但是我注意到此代码中的算法非常慢,并且根据抖动图像的大小,抖动过程可能需要一分多钟!
我已经应用了我能想到的所有优化 - 例如在游戏线程之外的单独线程中运行事物,并在线程完成时调用提供给函数的 Action 但这只会节省一点时间(如果有的话) .
所以我想知道是否有任何进一步的优化来使这个操作更快,如果可能的话总共几秒钟。我还想指出,在进行抖动操作时,我有明显的系统延迟——鼠标有时甚至会抖动和跳来跳去。对于那些必备的 60FPS PC 赛车大师来说,这并不酷。
最佳答案
首先想到的是处理 Bitmap
,因为它是数组。默认情况下,它不是一个选项,因为没有界面可以做到这一点,但你可以通过一些技巧来实现它。快速搜索跟随我到this answer .因此,您必须将方法设置为 unsafe
,使用 LockBits
获取像素值,并使用指针数学访问它们(完整代码请参阅原始答案):
System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
bmp.PixelFormat);
var pt = (byte*)bmpData.Scan0;
// for loop
var row = pt + (y * bmpData.Stride);
var pixel = row + x * bpp; // bpp is a number of dimensions for the bitmap
pixel
将是一个数组,其中包含有关以 byte
值编码的颜色信息。正如您已经看到的,GetPixel
和 SetPixel
速度很慢,因为它们实际上调用了 LockBits
来确保操作。 Array 将帮助您删除读取操作,但是,“SetPixel”仍然可能是瓶颈,因为您可能需要尽快更新位图。如果您可以在最后一次性更新它,那就去做吧。
第二个想法是创建一些 Task
队列,它将逐步更新您的数组。如我所见,您从一个角度更新图像,因此,也许您可以设置更新的并行版本。也许你可以创建一个带有版本控制的当前状态的不可变数组,所以最后你只需总结 bmp 的新版本。
关于c# - 我的抖动算法 super 慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41476000/
问题比较Java,但是我想在Android中实现: 假设有 3 个或更多类相互扩展: class A { ... int color; ... } class B extend
我知道标题听起来有点奇怪,但这正是我愿意做的。简单解释:A类是B类的子类,B类也是C类的子类>. 现在,所有这些类都包含方法m()。在我的 A 类中,这是我唯一可以访问的类,因为其他类仅在运行时可用,
我有一个 UIViewController 类 A 和 B。 A 使用以下方式加载 B:[A.view addSubView B.view]。 B 有一个带有“后退”按钮的导航栏。我想在单击时返回到
我有以下(第三方)类结构。我们将调用第三方项目 ProjectSeriously,并注意我使用 System.out.println 代替其他复杂的功能(100 行代码) . class A {
在下面的代码中,我从 Game 扩展了 MyGame。我有两个问题: 我们是否需要为所有render()、dispose()、pause()调用super方法 和 resize(w,h)?很多人都没有
例如,假设我想在调用 super.viewDidLoad() 时跳过一级。所以我希望能够做这样的事情: override func viewDidLoad() { super.super.vi
public class Faculty extends Employee { public static void main(String[] args) { new Fac
假设我有: class Superclass { //fields... methodA() {...} methodB() {...} ... } class Subclass exte
这个问题在这里已经有了答案: Why is super.super.method(); not allowed in Java? (22 个答案) 关闭 9 年前。 我怀疑我想做的事情是否可行。我有
我有一个实现 Initializable 的类。 public abstract class ExampleClass implements Initializable { public vo
我想知道,我有这个大数组,是否可以只在内存中使用一次而不是每个线程一次?以 stackoverflow 上的标签为例。他们几乎从不改变,为什么不为他们留下一个内存点呢?甚至可能将该数组永久保存在内存中
假设这三个类具有这个简单的层次结构: class A { func foo() { print("A") } } class B: A { override fu
有没有办法在 TypeScript 中调用 super.super.methodName。我想避免调用super.methodName,但我想调用二祖的methodName方法。 谢谢。 最佳答案 T
这个问题已经有答案了: When do I use super()? (11 个回答) 已关闭 7 年前。 package Geometry; public abstract class Geomet
我必须执行and()在我的实现 Predicate 的业务对象上. 出现问题的代码是 and() 行调用: Predicate predicate = new M
我有一个实现接口(interface)的抽象父类(super class): public abstract class FooMatrix implements Matrix { publi
我有四个 UIView:viewA 是 Root View ,它有 viewB 作为它的 subview 。 viewB 将 viewC 作为其 subview ,而 viewC 将 viewD 作为
有什么区别: class Child(SomeBaseClass): def __init__(self): super(Child, self).__init__() 和:
我有一个通用接口(interface) interface ListList extends List> .由于某些原因,我无法转换 ListList至 List> .有什么方法可以做到吗?为什么它不
我想调用带有两个参数的父类(super class)的构造函数,所以我调用了 super(arguments),但是编译器说: “类 Person 中的构造函数 Person 不能应用于给定类型; 要
我是一名优秀的程序员,十分优秀!