# 图像功能实现

  邮箱:yiyangyu1314@outlook.com
  
  作者:易陽羽
  
  时间:2023-06-08
1
2
3
4
5

# 1.用C#把Mono12Packed的buffer 保存为16位的tiff 图像

用C#中的System.Drawing.Imaging命名空间下的BitmapBitmapDataEncoder类将 Mono12Packed 的 buffer 保存为 16 位的 tiff 图像。以下是大致的代码流程:

// 假设已经将 Mono12Packed 数据读入到了 byte[] buffer 中

// 设置图像宽度和高度(根据实际情况设置)
int width = 2560;
int height = 2160;

// 创建一个 Bitmap 对象并锁定其内存区域以便于直接访问数据
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format16bppGrayScale);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bmp.PixelFormat);

// 写入像素数据(将 Mono12Packed 转换为 16 位灰度像素)
for (int y = 0; y < height; y++)
{
    byte* pRow = (byte*)bmpData.Scan0 + y * bmpData.Stride;
    for (int x = 0; x < width; x += 2)
    {
        byte b1 = buffer[(y * width + x) / 2 * 3];
        byte b2 = buffer[(y * width + x) / 2 * 3 + 1];

        ushort val1 = (ushort)((b1 << 4) | (b2 >> 4));
        ushort val2 = (ushort)(((b2 & 0xF) << 8) | buffer[(y * width + x) / 2 * 3 + 2]);
        *(ushort*)(pRow + x * 2) = val1;
        *(ushort*)(pRow + x * 2 + 2) = val2;
    }
}

// 解锁 Bitmap 对象并保存为 tiff 图像
bmp.UnlockBits(bmpData);
EncoderParameters encParams = new EncoderParameters(1);
encParams.Param[0] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.Flush);
bmp.Save("output.tiff", GetEncoder(ImageFormat.Tiff), encParams);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

需要注意的是,上面代码中的 GetEncoder 方法需要自行实现。它通常会在整个应用程序范围内使用, 并将每个图像格式映射到相应的图像编码器。以下是一个简单实现:

private static ImageCodecInfo GetEncoder(ImageFormat format)
{
    ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();

    foreach (ImageCodecInfo codec in codecs)
    {
        if (codec.FormatID == format.Guid)
        {
            return codec;
        }
    }

    return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

参考资料:

# 2.把Mono12Packed的buffer 保存为16位的tiff 图像,封装为动态库dll

以下是一个用C++将Mono12Packed的buffer保存为16位tiff图像的代码,并封装成动态库dll:

#include <fstream>
#include <iostream>
#include <vector>
#include <cstdint>
#include "tiffio.h"

extern "C" {
    __declspec(dllexport) void SaveAsTiff(uint8_t* buffer, uint32_t width, uint32_t height, const char* path)
    {
        // 打开TIFF文件
        TIFF* out = TIFFOpen(path, "w");

        if (out == NULL) {
            std::cerr << "Failed to create TIFF file." << std::endl;
            return;
        }

        // 配置TIFF文件头信息
        TIFFSetField(out, TIFFTAG_IMAGEWIDTH, width);
        TIFFSetField(out, TIFFTAG_IMAGELENGTH, height);
        TIFFSetField(out, TIFFTAG_SAMPLESPERPIXEL, 1);
        TIFFSetField(out, TIFFTAG_BITSPERSAMPLE, 16);
        TIFFSetField(out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
        TIFFSetField(out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
        TIFFSetField(out, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(out, width * sizeof(uint16_t)));

        // 写入像素数据(将 Mono12Packed 转换为 16 位灰度像素)
        std::vector<uint16_t> data(width * height);
        for (uint32_t y = 0; y < height; y++)
        {
            for (uint32_t x = 0; x < width; x += 2)
            {
                uint8_t b1 = buffer[(y * width + x) / 2 * 3];
                uint8_t b2 = buffer[(y * width + x) / 2 * 3 + 1];

                uint16_t val1 = (uint16_t)((b1 << 4) | (b2 >> 4));
                uint16_t val2 = (uint16_t)(((b2 & 0xF) << 8) | buffer[(y * width + x) / 2 * 3 + 2]);

                data[y * width + x] = val1;
                data[y * width + x + 1] = val2;
            }
        }

        TIFFWriteEncodedStrip(out, 0, data.data(), width * height * sizeof(uint16_t));

        // 关闭TIFF文件
        TIFFClose(out);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

代码中使用了 libtiff 库来操作 TIFF 文件,需要在项目中添加 -ltiff 库链接参数。 同时,请注意在编译动态库时添加合适的选项以确保导出函数符号。 这个代码可以编译为 Windows 或 Linux 平台下的动态库,在其他 C++ 项目中引入并 调用 SaveAsTiff 函数即可实现将Mono12Packed数据保存为16位tiff图像的功能。

参考资料:

# 3.用C++ 把Mono12Packed的buffer 保存为16位的tiff 图像,封装为动态库dll,并使用C#调用存图接口

以下是调用 C++ 动态库的示例 C# 代码,用于保存 Mono12Packed 数据为 16 位 TIFF 格式图像:

using System;
using System.Runtime.InteropServices;

namespace SaveAsTiffDemo
{
    class Program
    {
        [DllImport("libMono12PackedToTiff.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void SaveAsTiff(IntPtr buffer, uint width, uint height, string path);

        static void Main(string[] args)
        {
            // Mono12Packed 数据(假设为 2560x2160 分辨率)
            byte[] mono12PackedData = new byte[2560 * 2160 / 2 * 3];
            
            // 填充 Mono12Packed 数据(此处略去)
            // ......
            
            // 将 Mono12Packed 数据传递给 C++ 动态库
            IntPtr ptr = Marshal.AllocHGlobal(mono12PackedData.Length);
            Marshal.Copy(mono12PackedData, 0, ptr, mono12PackedData.Length);

            // 调用 C++ 存图接口
            SaveAsTiff(ptr, 2560, 2160, "output.tif");

            // 释放内存
            Marshal.FreeHGlobal(ptr);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

需要注意的是,C++ 动态库中的函数必须按照 C 风格进行导出,即使用 extern "C" { ... } 块来包含函数声明,并且在调用时需要指定 CallingConvention = CallingConvention.Cdecl 参数。同时,需要将 Mono12Packed 数据转换为 IntPtr 类型的指针传递给 C++ 函数。 完成上述代码后,在 C# 项目中添加对动态库文件的引用,并在调用前确保动态库文件已经正确部署到目标系统中即可。

# 4.用C++ 把Mono12的buffer 保存为16位的bmp,tiff,png,raw,jpeg 文件,封装为动态库dll,并使用C#,JAVA Jna调用存图接口

保存 Mono12 数据为 16 位图像的C++代码示例:

#include <cstdint>
#include <cstring>
#include <iostream>
#include <fstream>
#include "libtiff/tiffio.h"
#include "lodepng.h"

// 保存 Mono12 数据为 16 位 TIFF 格式图像
void SaveAsTiff(const uint8_t* buffer, uint32_t width, uint32_t height, const char* path)
{
    // 打开 TIFF 文件并写入数据
    TIFF* tif = TIFFOpen(path, "w");
    TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width);
    TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height);
    TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);
    TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 16);
    TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
    TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
    TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
    TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, width * sizeof(uint16_t)));
    for (uint32_t i = 0; i < height; ++i)
    {
        TIFFWriteScanline(tif, (uint16_t*)(buffer + i * width), i);
    }
    TIFFClose(tif);
}

// 保存 Mono12 数据为 16 位 PNG 格式图像(使用 lodepng 库)
void SaveAsPng(const uint8_t* buffer, uint32_t width, uint32_t height, const char* path)
{
    std::vector<uint16_t> data16(width * height);
    for (uint32_t i = 0; i < width * height; ++i)
    {
        data16[i] = buffer[i] << 8;
    }
    std::vector<uint8_t> data8(data16.size() * 2);
    std::memcpy(data8.data(), data16.data(), data16.size() * sizeof(uint16_t));
    lodepng::encode(path, data8, width, height, LCT_GREY_ALPHA, 16);
}

// 保存 Mono12 数据为 16 位 RAW 格式图像
void SaveAsRaw(const uint8_t* buffer, uint32_t width, uint32_t height, const char* path)
{
    std::ofstream fout(path, std::ios::binary);
    fout.write((const char*)(buffer), width * height * sizeof(uint16_t));
    fout.close();
}

// 保存 Mono12 数据为 16 位 JPEG 格式图像(使用 libjpeg-turbo 库)
void SaveAsJpeg(const uint8_t* buffer, uint32_t width, uint32_t height, const char* path)
{
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);

    FILE* outfile = fopen(path, "wb");
    if (!outfile)
    {
        std::cerr << "Failed to open file for writing " << path << std::endl;
        return;
    }

    jpeg_stdio_dest(&cinfo, outfile);
    cinfo.image_width = width;
    cinfo.image_height = height;
    cinfo.input_components = 1;
    cinfo.in_color_space = JCS_GRAYSCALE;
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, 90, TRUE);
    jpeg_start_compress(&cinfo, TRUE);

    JSAMPROW row_pointer[1];
    while (cinfo.next_scanline < cinfo.image_height)
    {
        row_pointer[0] = (JSAMPLE*)(buffer + cinfo.next_scanline * width);
        jpeg_write_scanlines(&cinfo, row_pointer, 1);
    }
    jpeg_finish_compress(&cinfo);
    fclose(outfile);
    jpeg_destroy_compress(&cinfo);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

需要注意的是,上述函数中使用的 TIFF 和 JPEG 库需要自行安装并链接到项目中。同时, 在编译 DLL 文件时需要将上述函数导出,例如:

// 定义 DLL 导出宏
#ifdef MAKEDLL
#define DLL __declspec(dllexport)
#else
#define DLL __declspec(dllimport)
#endif

// 导出函数声明
extern "C" {
    DLL void SaveAsTiff(const uint8_t* buffer, uint32_t width, uint32_t height, const char* path);
    DLL void SaveAsPng(const uint8_t* buffer, uint32_t width, uint32_t height, const char* path);
    DLL void SaveAsRaw(const uint8_t* buffer, uint32_t width, uint32_t height, const char* path);
    DLL void SaveAsJpeg(const uint8_t* buffer, uint32_t width, uint32_t height, const char* path);
}

// 导出函数定义
DLL void SaveAsTiff(const uint8_t* buffer, uint32_t width, uint32_t height, const char* path)
{
    // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

完成 DLL 文件编译后,在 C# 项目中可以使用以下方式调用存图接口:

using System;
using System.Runtime.InteropServices;

namespace SaveAsImageDemo
{
    class Program
    {
        // 导入 DLL 中的函数
        [DllImport("SaveAsImage.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void SaveAsTiff(IntPtr buffer, uint width, uint height, string path);

        [DllImport("SaveAsImage.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void SaveAsPng(IntPtr buffer, uint width, uint height, string path);

        [DllImport("SaveAsImage.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void SaveAsRaw(IntPtr buffer, uint width, uint height, string path);

        [DllImport("SaveAsImage.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void SaveAsJpeg(IntPtr buffer, uint width, uint height, string path);

        static void Main(string[] args)
        {
            // 假设 Mono12 数据存放在 byte[] buffer 中,分辨率为 2560x2160
            byte[] buffer = new byte[2560 * 2160];
            
            // 将 Mono12 数据传递给 C++ DLL 中的函数
            IntPtr ptr = Marshal.AllocHGlobal(buffer.Length);
            Marshal.Copy(buffer, 0, ptr, buffer.Length);

            // 调用 C++ DLL 中的存图接口
            SaveAsTiff(ptr, 2560, 2160, "output.tif");
            SaveAsPng(ptr, 2560, 2160, "output.png");
            SaveAsRaw(ptr, 2560, 2160, "output.raw");
            SaveAsJpeg(ptr, 2560, 2160, "output.jpg");

            // 释放内存
            Marshal.FreeHGlobal(ptr);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

在 JAVA 中,可以使用 JNA(Java Native Access)库调用 C++ DLL 中的函数,具体操作方式略有 不同,可以参考 JNA 的相关文档进行学习。