2010-01-04 5 views

답변

2

이것은 양자화라고 불리며 복잡합니다. 나는이 문제를 광범위하게 다루었으며, Octree 양자화와 사용자 정의 확산 알고리즘을 사용하여 최상의 결과를 얻었습니다.

A부터 B까지의 가장 빠른 지점은 grab my code (open-source, but $69 to download)이며 매우 간단한 API를 사용하여 색상 수를 16으로 설정하고 GIF 또는 PNG로 저장하십시오. 이 파일 시스템에 있다면 당신은 쿼리 문자열을 사용할 수 있습니다, 당신은 코드 숨김을 통해 작업을 수행하려는 경우 코드의 약 2 라인 수 ... 또는해야 :

image.bmp?format=gif&colors=16 

이미지가 이미 그레이 스케일없는 경우 모듈의 ImageAttributes 클래스를 사용하여이를 수행 할 수 있습니다. 결과 GIF는 자동으로 회색 음영 팔레트를 갖습니다. 최소한의 작업, 훌륭한 결과.

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

.... http://codebetter.com/blogs/brendan.tompkins/archive/2007/06/14/gif-image-color-quantizer-now-with-safe-goodness.aspx

는 의견을 읽고 내 의견에 따라 포인터 연산 오류를 패치 없음 디더링 :

당신은 여기에 내가 시작 무엇을, 자신의 롤하지 않으려면 완전 신뢰 환경보다 적게 원본을 실행하는 데 문제가있을 수 있습니다. 나는 수년에 걸쳐 많은 패치를 만들었고, 나는 그것들 모두를 기억하지 못합니다.

+0

octree 및 기타 algos를 포함한 더 많은 무료 예제는 codeplex.com 및 code.msdn.com을 확인하십시오. –

+2

어떤 종류의 "오픈 소스"소프트웨어를 다운로드하려면 $ 69가 필요합니까? – TAG

+0

@ 환불 불가 - 예, MSDN에는 일부가 있지만 codebetter.com보다 나이가 들리고 버그가 있습니다. 기사를 읽으면 그가 시작했다. @TAG 많은 오픈 소스 소프트웨어. 많은 리눅스 배포판처럼. 오픈 소스는 무료 프로젝트가 아니라 다른 프로젝트의 일부로 재배포 가능합니다. 나는 라이센스 번거 로움을 싫어, 나는 다른 devs도 할 그림. –

1

또 다른 가능성은 오픈 소스 코드를 뒤적 거리지 않고 Paint.Net을 다운로드하는 것입니다. 나는 그것이 그레이 스케일로 변환 할 수 있다고 믿지만, 나는 그것을 사용해야 할 필요가 있기 때문에 오랫동안 잘못 되었기 때문에 잘못 될 수있다.

0

일단 도구 세트가 있으면 실제로 그다지 어렵지 않습니다. 그리고 그 중 상당수를 구축했습니다. 필요한 항목은 다음과 같습니다.

  • 16 색 그레이 스케일 팔레트.
  • 가장 가까운 색상 이미지 데이터와 일치하는 기능입니다 4 비트 데이터에 이러한 일치 (값에 따라 반 바이트)로 데이터를 기록 할
  • 하는 방식의 변환
  • 하는 함수 (팔레트 데이터를 얻을 수 있습니다) 새로운 4 비트 이미지 객체

팔레트는 쉽습니다. 회색 값은 빨강, 녹색 및 파랑에 대해 동일한 값을 갖는 색상이며, 16 색의 색상 간 밝기 단계가 동일 할 경우 해당 값은 0x00, 0x11, 0x22 등의 범위에서 0xFF까지입니다. 만들 어렵지 않아야합니다.

다음 단계는 이미지 색상을 팔레트 색상과 일치시키고 이러한 값의 바이트 배열을 만드는 것입니다. 가장 가까운 성냥을 이미 stackoverflow에서 사용할 수있게하는 몇 가지 방법이 있습니다.

How to compare Color object and get closest Color in an Color[]?

다음은 까다로운 부분을 제공 : 4 비트에 실제 이미지 데이터를 변환이 질문은 그들의 무리가있다.

한 가지 유의할 점은 이미지가 한 줄에 저장된다는 것과 그와 같은 선 ("스캔 라인"이라고도 함)은 반드시 이미지와 동일한 너비가 아니어야한다는 것입니다. 예를 들어, 픽셀 당 4 비트에서 각 바이트에 2 픽셀을 맞출 수 있으므로 논리적으로 보폭은 2로 나눈 값이됩니다. 그러나 너비가 고르지 않은 숫자 인 경우 각 행 끝에 바이트가 있습니다. 절반 만 채웠다. 시스템은 다음 라인의 첫 번째 픽셀을 거기에 두지 않습니다. 대신 단순히 공백으로 남겨 두었습니다. 또한 8 비트 또는 16 비트 이미지의 경우 스트라이드는 종종 스캔 라인을 4 바이트의 배수로 정렬합니다. 따라서 너비가 주사선 길이와 같다고 가정하지 마십시오.

필자는이 회신에 추가로 필요한 기능을 위해 필요한 최소 주사선 길이를 사용합니다. 이것은 8 배로 비트 길이를 나눈 값의 너비 배가되기 때문에 그 부분에 나머지가 있으면 1을 더한 것이므로 ((bpp * width) + 7)/8으로 쉽게 계산할 수 있습니다.

그레이 스케일 팔레트를 생성 한 다음 이미지의 각 픽셀에 가장 가까운 팔레트 값을 포함하는 바이트 배열을 만들면 실제 8 비트에서 4 비트로 변환하는 모든 값을 갖게됩니다.

나는 주어진 비트 길이로 8 비트 데이터를 변환하는 함수를 작성했다. 따라서 4 비트 이미지에는 bitsLength=4이 필요합니다.

BigEndian 매개 변수는 한 바이트 내의 값이 전환되는지 여부를 결정합니다. .Net 이미지에 대해서는 잘 모르겠지만, 1BPP 포맷은 빅 엔디 언 비트를 사용하는 반면, 가장 낮은 니블로 시작하는 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; 
} 

그리고 마지막으로 그 기능을 사용하는 메모리를 복사합니다. 보시다시피이 메서드는 지정된 스트라이드를 사용하여 줄 단위로 복사하므로 .Net 프레임 워크가 만드는 Bitmap에 사용되는 내부 보폭은 무시할 수 있습니다. 그것은 어쨌거나 같거나 클 것입니다.

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); 
    } 
} 
관련 문제