如何在获得1个特定像素的颜色时获得最佳性能,用于35张图像

本文关键字:性能 最佳 用于 图像 颜色 35张 像素 1个 | 更新日期: 2023-09-27 18:03:04

事情是这样的:我有一个ASP。. Net API在Azure上设置,由2部分组成:一个预定的作业,从外部网站获取35张图像(宽度:2000px,高度:1450px)并将其保存到我的服务器。第二部分是一个GET API,它有一个X和Y值作为参数。现在我想做的是:当用户输入X - Y时,我希望API遍历所有35张图像并获得该特定点的颜色。

我现在有这个:

string Data = "";

foreach (string FilePath in   Directory.GetFiles(HttpContext.Current.Server.MapPath("~/Content/Images/")))
{ 
     Bitmap ImageHolder = new Bitmap(FilePath);
     Color color = ImageHolder.GetPixel(PixelX, PixelY);
     string ColorString = color.R.ToString() + " " + color.G.ToString() + " " + color.B.ToString();
     string Time = Path.GetFileNameWithoutExtension(FilePath);
     Data = Data + Time + "|" + ColorString + " ";
}
return Data;

反应:

1015 | 1020 | 0 0 0 0 0 0 0 0 1030 1025 | 0 | 1035 | 0 0 0 0 0 0 1040 | 0 0 0 1050 | 1045 | 0 0 0 0 0 0 1055 | 1100 | 0 0 0 0 0 0 1105 | 0 0 0 1115 | 1110 | 0 0 0 0 0 0 1120 | 1125 | 0 0 0 0 0 0 1130 | 0 0 0 1140 | 1135 | 0 0 0 0 0 0 1145 | 1150 | 0 0 0 0 0 0 1155 | 0 0 0 1205 | 1200 | 0 0 0 0 0 0 1215 | 1210 | 0 0 0 0 0 0 1220 | 1225 | 0 0 0 0 0 0 1230 | 0 0 0 1240 | 1235 | 0 0 0 0 0 0 1245 | 1250 | 0 0 0 0 0 0

现在,这一切都工作了。但是,当我托管它并将其作为API使用时,响应时间有时超过6000毫秒。

通过使用秒表,我看到所有的代码都运行得很好,,但有时当代码运行时,它需要高达500毫秒:

Bitmap ImageHolder = new Bitmap(FilePath);

关于如何减少响应时间和加快进程有什么想法吗?预先计算调度的crone-job中的所有内容似乎是一种努力,因为图像非常大,并且在存储它们时,我们将讨论很多点。

如何在获得1个特定像素的颜色时获得最佳性能,用于35张图像

看起来你正在处理的文件是简单的24位图,对吗?

在这种情况下,您可以完全避免使用GDI+(在ASP中使用它是个坏主意)。NET),并直接解析位图数据。这意味着你甚至不需要读取整个文件——只需要读取头文件和你需要的任何像素。

如果你确实在使用简单的24位图,下面的代码应该可以很好地工作:

Color GetPixel(string fileName, int x, int y)
{
    var buffer = new byte[32];
    using (var file = File.OpenRead(fileName))
    {
        if (file.Read(buffer, 0, 32) < 32) return Color.Empty;
        // Bitmap type. Pretty much everything you find is BM.
        var type = Encoding.ASCII.GetString(buffer, 0, 2);
        if (type != "BM") return Color.Empty;
        // Data offset
        var offset = BitConverter.ToInt32(buffer, 10);
        // Windows bitmaps have width and height in a fixed place:
        var width = BitConverter.ToInt32(buffer, 18);
        var height = BitConverter.ToInt32(buffer, 22);
        if (width < x || height < y) return Color.Empty;
        // Three bytes per pixel, padded to multiples of four
        var rowSize = width * 3 + ((4 - ((width * 3) % 4)) % 4);
        // And get our pixel - since we're non-compressed, non-indexed, 
        // 32-bit pixels, this is easy. Note that bitmaps are usually stored
        // top to bottom:
        file.Seek(offset + ((rowSize * (height - y - 1)) + x * 3), SeekOrigin.Begin);
        if (file.Read(buffer, 0, 3) < 3) return Color.Empty;
        // Alpha
        buffer[3] = 0xFF;
        var color = BitConverter.ToInt32(buffer, 0);
        return Color.FromArgb(color);
    }
}

即使你的文件不是简单的24位图,你的cron-job也不应该有转换它们的问题——如果你想方便地索引到位图数据,你没有太多的选择:)

它只需要一些改变来支持8位索引位图:

Color GetPixel(string fileName, int x, int y)
{
    var buffer = new byte[32];
    using (var file = File.OpenRead(fileName))
    {
        if (file.Read(buffer, 0, 32) < 32) return Color.Empty;
        // Bitmap type. Pretty much everything you find is BM.
        var type = Encoding.ASCII.GetString(buffer, 0, 2);
        if (type != "BM") return Color.Empty;
        // Data offset
        var offset = BitConverter.ToInt32(buffer, 10);
        // Windows bitmaps have width and height in a fixed place:
        var width = BitConverter.ToInt32(buffer, 18);
        var height = BitConverter.ToInt32(buffer, 22);
        if (width < x || height < y) return Color.Empty;
        // One byte per pixel, padded to multiples of four
        var rowSize = width + ((4 - ((width) % 4)) % 4);
        // Now we're going to read an index into our palette
        file.Seek(offset + ((rowSize * (height - y - 1)) + x), SeekOrigin.Begin);
        if (file.Read(buffer, 0, 1) < 1) return Color.Empty;
        // Jump to the palette record and get the actual color
        file.Seek(54 + buffer[0] * 4, SeekOrigin.Begin);
        if (file.Read(buffer, 0, 4) < 4) return Color.Empty;
        var color = BitConverter.ToInt32(buffer, 0);
        return Color.FromArgb(color);
    }
}

如果你想避免编写位图解析代码,你只需要确保位图总是在内存中加载和解析——瓶颈不是GetPixel,而是从磁盘加载Bitmap

缓存每个图像的标题和调色板可能是值得的,以避免一些搜索-我不确定它是否会有所帮助,但基本思想是这样的:

private static readonly ConcurrentDictionary<string, byte[]> _cache;
Color GetPixel(string fileName, int x, int y)
{
  var buffer = new byte[3];
  using (var file = File.OpenRead(fileName))
  {
    byte[] headers;
    if (_cache.ContainsKey(fileName))
    {
      headers = _cache[fileName];
    }
    else
    {
      headers = new byte[1078];
      if (file.Read(headers, 0, headers.Length) < headers.Length) return Color.Empty;
      _cache.TryAdd(fileName, headers);
    }
    // Now read the headers as before, using the headers local instead of buffer
    // ...
    file.Seek(offset + ((rowSize * (height - y - 1)) + x), SeekOrigin.Begin);
    if (file.Read(buffer, 0, 1) < 1) return Color.Empty;
    var color = BitConverter.ToInt32(headers, 54 + buffer[0] * 4);
    return Color.FromArgb(color);
  }
}

这些都是非常大的图像,将花费可观的时间从磁盘读取,并且需要合理数量的RAM(在压缩期间可能永远无法恢复-"大对象堆碎片")。Bitmap.ctor(file)的内部只是传递给一个本地GDI方法-所以你的延迟完全是基于IO的。

实现目标的最快方法是根据你对文件格式的了解计算基于文件的像素偏移量,然后自己打开文件流,查找像素,然后只读取所需的数据。这有点复杂,但是这些文件格式都有很好的文档记录,并且可能有一个现有的文件库可供您使用。这里的关键是不要将整个文件解析为RAM。