ASP.NET에서 16 색 회색도 스케일 GIF 또는 PNG로 비트 맵을 어떻게 저장합니까?

StackOverflow https://stackoverflow.com/questions/2002322

  •  18-09-2019
  •  | 
  •  

문제

ASP.NET C#에서는 비트 맵 이미지를 16 색 비 투명한 회색조 스케일 이미지로 PNG 또는 GIF로 저장하려고합니다. 팔레트를 만들어야한다고 생각한 다음 어떻게 든 팔레트를 이미지에 첨부하지만이 작업을 수행하는 방법을 잘 모르겠습니다.

소스 이미지는 24 비트 컬러 비트 맵입니다.

도움이 되었습니까?

해결책

이를 양자화라고하며 복잡합니다. 나는이 문제로 광범위하게 일했으며 최상의 결과는 Octree Quantization과 사용자 정의 확산 알고리즘을 사용하는 것입니다.

A에서 B까지 가장 빠른 지점은입니다 내 코드를 잡아 매우 간단한 API를 사용하여 색 수를 16으로 설정하고 GIF 또는 PNG로 저장하십시오. Code-Behind를 통해 수행하려면 약 2 줄의 코드 여야합니다 ... 또는 파일 시스템에있는 경우 쿼리 스트링을 사용할 수 있습니다.

image.bmp?format=gif&colors=16

이미지가 아직 회색체가 아닌 경우 모듈의 ImageAttributes 클래스를 사용하여 수행 할 수 있습니다. 결과 GIF에는 자동으로 회색조 팔레트가 있습니다. 최소한의 작업, 훌륭한 결과.

httpmodule로 사용할 필요가 없다는 것을 기억하십시오. 주로 이미지를 조정, 수정 및 인코딩하기위한 라이브러리입니다.

당신이 당신의 자신을 굴려보고 싶다면, 여기에 내가 시작한 내용은 다음과 같습니다.http://codebetter.com/blogs/brendan.tompkins/archive/2007/06/14/gif-image-color-quantizer-with-safe-goodness.aspx

주석을 읽고 내 주석에 따라 포인터 산술 오류를 패치하십시오 ....

그러나 디더링은 없으며, 완전한 신뢰 환경에서 원본을 실행하는 데 어려움이있을 수 있습니다. 나는 수년에 걸쳐 많은 패치를 만들었고, 그들 모두를 기억하지 못합니다.

다른 팁

오픈 소스 코드를 통과하는 데 관심이 없다면 또 다른 가능성은 Paint.net을 다운로드하는 것입니다. 나는 그것이 그레이 스케일로 전환 할 수 있다고 생각하지만, 그것을 사용할 필요가 있었기 때문에 오랜 시간이 지났을 때 잘못 될 수 있습니다.

일단 도구 세트를 얻으면 실제로는 전혀 어렵지 않습니다. 필요한 것은 다음과 같습니다.

  • 16 컬러 회색조 팔레트.
  • 이미지 데이터를 가장 가까운 색상으로 일치시키는 함수 (Paletted 데이터를 얻기 위해)
  • 이 일치를 4 비트 데이터로 변환하는 함수 (값당 절반)
  • 해당 데이터를 새로운 4 비트 이미지 개체에 작성하는 방법.

팔레트는 쉽습니다. 회색 값은 빨간색, 녹색 및 파란색의 값이 동일한 색상이며 16 색의 색상 사이의 동일한 밝기 단계에 대해서는 값이 0x00, 0x11, 0x22 등까지 0xff입니다. 만들기가 어렵지 않아야합니다.

다음 단계는 이미지 색상을 팔레트 색상과 일치시키고 이러한 값의 바이트 배열을 만드는 것입니다. StackoverFlow에서 가장 가까운 경기를 사용할 수있는 몇 가지 방법이 있습니다. 이 질문은 많은 것들이 있습니다.

색상 대상을 비교하고 색상으로 가장 가까운 색상을 얻는 방법 []?

다음은 까다로운 부분이 있습니다 : 실제 이미지 데이터를 4 비트로 변환합니다.

명심해야 할 한 가지는 이미지가 줄 당 저장되며, 그러한 선 ( "스캔 라인")이 이미지와 같은 너비가 아니라는 것입니다. 예를 들어, 픽셀 당 4 비트로 각 바이트에 2 픽셀을 장착 할 수 있으므로 논리적으로 보폭은 2로 나뉩니다. 그러나 너비가 고르지 않은 숫자 인 경우 각 선은 끝에 바이트가 있습니다. 반으로 가득 찬. 시스템은 다음 줄의 첫 번째 픽셀을 거기에 넣지 않습니다. 대신 그것은 단순히 비워 두는 것입니다. 그리고 8 비트 또는 16 비트 이미지의 경우 보폭이 종종 스캔 라인을 4 바이트의 다중에 맞추는 것을 알고 있습니다. 따라서 너비가 스캔 라인 길이와 동일하다고 가정하지 마십시오.

이 응답에 더 내려 놓은 기능의 경우 필요한 최소 스캔 라인 길이를 사용합니다. 이것은 폭이 8로 나뉘어지고 그 부서에 나머지가있는 경우 1 개가 8로 나뉘어져 있기 때문에 다음과 같이 쉽게 계산할 수 있습니다. ((bpp * width) + 7) / 8.

이제 Greyscale 팔레트를 생성 한 다음 이미지의 각 픽셀에 가장 가까운 팔레트 값을 포함하는 바이트 배열을 만든 경우 실제 8 비트에서 4 비트 변환 기능에 공급할 모든 값이 있습니다.

8 비트 데이터를 특정 비트 길이로 변환하는 기능을 작성했습니다. 그래서 이것은 필요할 것입니다 bitsLength=4 4 비트 이미지의 경우.

Bigendian 매개 변수는 1 바이트 내부의 값이 전환되는지 여부를 결정합니다. 여기서 .NET 이미지는 확실하지 않지만 1BPP 형식이 많은 경우 Big-Endian 비트를 사용하는 반면 대신 가장 낮은 니블로 시작한 4BPP 형식을 발견했습니다.

    /// <summary>
    /// Converts given raw image data for a paletted 8-bit image to lower amount of bits per pixel.
    /// </summary>
    /// <param name="data8bit">The eight bit per pixel image data</param>
    /// <param name="width">The width of the image</param>
    /// <param name="height">The height of the image</param>
    /// <param name="newBpp">The new amount of bits per pixel</param>
    /// <param name="stride">Stride used in the original image data. Will be adjusted to the new stride value.</param>
    /// <param name="bigEndian">Values inside a single byte are read from the largest to the smallest bit.</param>
    /// <returns>The image data converted to the requested amount of bits per pixel.</returns>
private static Byte[] ConvertFrom8Bit(Byte[] data8bit, Int32 width, Int32 height, Int32 bitsLength, Boolean bigEndian)
    {
        if (newBpp > 8)
            throw new ArgumentException("Cannot convert to bit format greater than 8!", "newBpp");
        if (stride < width)
            throw new ArgumentException("Stride is too small for the given width!", "stride");
        if (data8bit.Length < stride * height)
            throw new ArgumentException("Data given data is too small to contain an 8-bit image of the given dimensions", "data8bit");
    Int32 parts = 8 / bitsLength;
    // Amount of bytes to write per width
    Int32 stride = ((bpp * width) + 7) / 8;
    // Bit mask for reducing original data to actual bits maximum.
    // Should not be needed if data is correct, but eh.
    Int32 bitmask = (1 << bitsLength) - 1;
    Byte[] dataXbit = new Byte[stride * height];
    // Actual conversion porcess.
    for (Int32 y = 0; y < height; y++)
    {
        for (Int32 x = 0; x < width; x++)
        {
            // This will hit the same byte multiple times
            Int32 indexXbit = y * stride + x / parts;
            // This will always get a new index
            Int32 index8bit = y * width + x;
            // Amount of bits to shift the data to get to the current pixel data
            Int32 shift = (x % parts) * bitsLength;
            // Reversed for big-endian
            if (bigEndian)
                shift = 8 - shift - bitsLength;
            // Get data, reduce to bit rate, shift it and store it.
            dataXbit[indexXbit] |= (Byte)((data8bit[index8bit] & bitmask) << shift);
        }
    }
    return dataXbit;
}

다음 단계는 올바른 치수와 픽셀 형식의 이미지를 만들고 메모리에 백업 배열을 열고 데이터를 덤프하는 것입니다. 16 컬러 이미지의 픽셀 형식은 다음과 같습니다 PixelFormat.Format4bppIndexed.

/// <summary>
/// Creates a bitmap based on data, width, height, stride and pixel format.
/// </summary>
/// <param name="sourceData">Byte array of raw source data</param>
/// <param name="width">Width of the image</param>
/// <param name="height">Height of the image</param>
/// <param name="stride">Scanline length inside the data</param>
/// <param name="pixelFormat"></param>
/// <param name="palette">Color palette</param>
/// <returns>The new image</returns>
public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat, Color[] palette)
{
    if (width == 0 || height == 0)
        return null;
    Bitmap newImage = new Bitmap(width, height, pixelFormat);
    BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat);
    CopyMemory(targetData.Scan0, sourceData, sourceData.Length, stride, targetData.Stride);
    newImage.UnlockBits(targetData);
    // For 8-bit images, set the palette.
    if ((pixelFormat == PixelFormat.Format8bppIndexed || pixelFormat == PixelFormat.Format4bppIndexed) && palette != null)
    {
        ColorPalette pal = newImage.Palette;
        for (Int32 i = 0; i < pal.Entries.Length; i++)
            if (i < palette.Length)
            pal.Entries[i] = palette[i];
        newImage.Palette = pal;
    }
    return newImage;
}

그리고 마지막으로, 그에 의해 사용 된 메모리를 복사 할 수있는 기능. 보시다시피,이 방법은 구체적으로 인수로 주어진 보폭을 사용하여 라인별로 복사하므로 Bitmap .NET 프레임 워크를 생성하는 것은 무시할 수 있습니다. 어쨌든 동일하거나 클 것입니다.

public static void CopyMemory(IntPtr target, Byte[] sourceBytes, Int32 length, Int32 origStride, Int32 targetStride)
{
    IntPtr unmanagedPointer = Marshal.AllocHGlobal(sourceBytes.Length);
    Marshal.Copy(sourceBytes, 0, unmanagedPointer, sourceBytes.Length);
    CopyMemory(target, unmanagedPointer, length, origStride, targetStride);
    Marshal.FreeHGlobal(unmanagedPointer);
}

public static void CopyMemory(IntPtr target, IntPtr source, Int32 length, Int32 origStride, Int32 targetStride)
{
    IntPtr sourcePos = source;
    IntPtr destPos = target;
    Int32 minStride = Math.Min(origStride, targetStride);
    Byte[] imageData = new Byte[targetStride];
    while (length >= origStride && length > 0)
    {
        Marshal.Copy(sourcePos, imageData, 0, minStride);
        Marshal.Copy(imageData, 0, destPos, targetStride);
        length -= origStride;
        sourcePos = new IntPtr(sourcePos.ToInt64() + origStride);
        destPos = new IntPtr(destPos.ToInt64() + targetStride);
    }
    if (length > 0)
    {
        Marshal.Copy(sourcePos, imageData, 0, length);
        Marshal.Copy(imageData, 0, destPos, length);
    }
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top