C # маршалинг данных изображения - сложный сценарий - PullRequest
1 голос
/ 16 декабря 2010

Я нашел библиотеку, написанную на C ++, которая фильтрует изображения с обработкой сигналов NTSC.Вы можете увидеть это здесь: http://slack.net/~ant/libs/ntsc.html#nes_ntsc Цель состоит в том, чтобы изображение выглядело так, как будто оно было выведено NES на телевизор.

Я хочу обернуть версию библиотеки SNES в C # (на самом деле, я бы выполнил версию NES, но она работает только с данными палитры NTSC, а не с растровыми изображениями. После нескольких часов игры с кодом я признал поражение и пришел ко всем вам за помощью.

Вот часть кода C ++ в библиотеке.Я добавил dllexports.

/* Image parameters, ranging from -1.0 to 1.0. Actual internal values shown
in parenthesis and should remain fairly stable in future versions. */
typedef struct snes_ntsc_setup_t
{
    /* Basic parameters */
    double hue;        /* -1 = -180 degrees     +1 = +180 degrees */
    double saturation; /* -1 = grayscale (0.0)  +1 = oversaturated colors (2.0) */
    double contrast;   /* -1 = dark (0.5)       +1 = light (1.5) */
    double brightness; /* -1 = dark (0.5)       +1 = light (1.5) */
    double sharpness;  /* edge contrast enhancement/blurring */

    /* Advanced parameters */
    double gamma;      /* -1 = dark (1.5)       +1 = light (0.5) */
    double resolution; /* image resolution */
    double artifacts;  /* artifacts caused by color changes */
    double fringing;   /* color artifacts caused by brightness changes */
    double bleed;      /* color bleed (color resolution reduction) */
    int merge_fields;  /* if 1, merges even and odd fields together to reduce flicker */
    float const* decoder_matrix; /* optional RGB decoder matrix, 6 elements */

    unsigned long const* bsnes_colortbl; /* undocumented; set to 0 */
} snes_ntsc_setup_t;

enum { snes_ntsc_entry_size = 128 };
enum { snes_ntsc_palette_size = 0x2000 };
typedef unsigned long snes_ntsc_rgb_t;
struct snes_ntsc_t {
    snes_ntsc_rgb_t table [snes_ntsc_palette_size] [snes_ntsc_entry_size];
};

/* Initializes and adjusts parameters. Can be called multiple times on the same
snes_ntsc_t object. Can pass NULL for either parameter. */
typedef struct snes_ntsc_t snes_ntsc_t;
__declspec(dllexport) void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup );

/* Filters one or more rows of pixels. Input pixel format is set by SNES_NTSC_IN_FORMAT
and output RGB depth is set by SNES_NTSC_OUT_DEPTH. Both default to 16-bit RGB.
In_row_width is the number of pixels to get to the next input row. Out_pitch
is the number of *bytes* to get to the next output row. */
__declspec(dllexport) void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input,
    long in_row_width, int burst_phase, int in_width, int in_height,
    void* rgb_out, long out_pitch );

Вот фрагмент из демонстрационного кода C ++.Он использует SDL.

typedef struct image_t
{
    unsigned char const* byte_pixels;/* 8-bit pixels */
    unsigned short const* rgb_16;    /* 16-bit pixels */
    int width;
    int height;
    int row_width; /* number of pixels to get to next row (may be greater than width) */
} image_t;

image_t image;
int burst_phase = 0;
snes_ntsc_setup_t setup = snes_ntsc_composite;

snes_ntsc_t* ntsc = (snes_ntsc_t*) malloc( sizeof (snes_ntsc_t) );
if ( !ntsc )
    fatal_error( "Out of memory" );
snes_ntsc_init( ntsc, &setup );

load_bmp( &image, (argc > 1 ? argv [1] : "test.bmp"), 0 );
init_window( SNES_NTSC_OUT_WIDTH( image.width ), image.height * 2 );

// lock the SDL image surface elsewhere...
output_pitch = surface->pitch;
output_pixels = (unsigned char*) surface->pixels;

burst_phase = 0;

snes_ntsc_blit( ntsc, image.rgb_16, image.row_width, burst_phase,
    image.width, image.height, output_pixels, output_pitch );

SNES_NTSC_OUT_WIDTH - это макрос, который возвращает 441, если вы введете 256.

Кроме того, библиотека встроена (по умолчанию) для работы с 16 битами на пиксель как ви вне, в шаблоне 6 5 6.

Используя все эти данные из документов, определений и typedefs, вот моя попытка некоторых из C #:

    [DllImport("snes.dll")]
    internal static extern void snes_ntsc_init(snes_ntsc_t t, snes_ntsc_setup_t setup);

    [DllImport("snes.dll")]
    internal static extern void snes_ntsc_blit(snes_ntsc_t ntsc, IntPtr input,
            long in_row_width, int burst_phase, int in_width, int in_height,
            [Out]IntPtr rgb_out, [Out]long out_pitch);

[StructLayout(LayoutKind.Sequential)]
internal class snes_ntsc_t
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = (0x2000 * 128))]
    public ulong[] table;
}

[StructLayout(LayoutKind.Sequential)]
internal struct snes_ntsc_setup_t
{
    /* Basic parameters */
    public double hue;        /* -1 = -180 degrees     +1 = +180 degrees */
    public double saturation; /* -1 = grayscale (0.0)  +1 = oversaturated colors (2.0) */
    public double contrast;   /* -1 = dark (0.5)       +1 = light (1.5) */
    public double brightness; /* -1 = dark (0.5)       +1 = light (1.5) */
    public double sharpness;  /* edge contrast enhancement/blurring */

    /* Advanced parameters */
    public double gamma;      /* -1 = dark (1.5)       +1 = light (0.5) */
    public double resolution; /* image resolution */
    public double artifacts;  /* artifacts caused by color changes */
    public double fringing;   /* color artifacts caused by brightness changes */
    public double bleed;      /* color bleed (color resolution reduction) */
    public int merge_fields;  /* if 1, merges even and odd fields together to reduce flicker */
    public float decoder_matrix; /* optional RGB decoder matrix, 6 elements */

    public ulong bsnes_colortbl; /* undocumented; set to 0 */
}

// Inside my main function...

        snes_ntsc_t t = new snes_ntsc_t();
        t.table = new ulong[0x2000 * 128];

        snes_ntsc_setup_t setup = new snes_ntsc_setup_t();
        setup.merge_fields = 1;

        snes_ntsc_init(t, setup);

        Bitmap orig = (Bitmap)Bitmap.FromFile("test.png");
        Bitmap image = new Bitmap(orig.Width, orig.Height, PixelFormat.Format16bppRgb565);
        using (Graphics g = Graphics.FromImage(image)) g.DrawImage(orig, new Rectangle(0, 0, orig.Width, orig.Height));
        orig.Dispose();

        BitmapData bits = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format16bppRgb565);

        // this image size is given in the demo
        Bitmap output = new Bitmap(441, 448);

        BitmapData outbits = output.LockBits(new Rectangle(0, 0, output.Width, output.Height), ImageLockMode.WriteOnly, PixelFormat.Format16bppRgb565);

        IntPtr outscan = Marshal.AllocHGlobal(outbits.Height * outbits.Width * 2);
        snes_ntsc_blit(t, bits.Scan0, bits.Stride, 0, bits.Width, bits.Height, outscan, outbits.Stride);
        // copy back to Scan0, if that's correct. Help with that too?

        image.UnlockBits(bits);

Итак, проблема в том, что в строке snes_ntsc_blit я получаю AccessViolationException.Эта поразительно бесполезная ошибка в основном является ошибкой, но я понятия не имею, какую из десятков возможных ошибок я допустил:

  1. Неправильно ли выделена моя таблица snes_ntsc_t?Может быть, мне стоит использовать для этого маршала?
  2. Может быть размер моего изображения (602x448) может быть неправильным?Я все еще получу эту ошибку, если я сделаю ее слишком большой, или это будет возможной защитой, чтобы устранить ее как ошибку?
  3. Верны ли мои объявления структуры?Я не знаю, может быть, нужно выполнить еще какие-то действия, если у меня неправильные типы, или нужно что-то другое из параметров.
  4. Верны ли мои параметры blit?Я понятия не имею, но похоже, что это соответствует тому, что спрашивают.
  5. Нужно ли как-то маршалировать растровые данные?Если да, пожалуйста, объясните?

Извините, что опубликовал такой огромный вопрос, но сортировка - проклятие моей карьеры в C #.Я ненавижу это.Пожалуйста, помогите.

Редактировать

Я установил контрольные точки и смог войти в C-код.Мои параметры для вызова init в настоящее время в порядке, но для blit мой входной параметр все испорчен.Наведение над ним в VS (на стороне C) показывает, что выглядит как адрес, а затем какой-то юникодный мусор.Я не могу сказать, нормально это или нет.Ошибка происходит во время строки SNES_NTSC_RGB_OUT внутреннего цикла for, но не при первом проходе.Упомянутый макрос выполняет кучу математических операций, а затем присваивает второй параметр.Итак, вот код функции blit:

void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
    int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
{
    int chunk_count = (in_width - 1) / snes_ntsc_in_chunk;
    for ( ; in_height; --in_height )
    {
        SNES_NTSC_IN_T const* line_in = input;
        SNES_NTSC_BEGIN_ROW( ntsc, burst_phase,
            snes_ntsc_black, snes_ntsc_black, SNES_NTSC_ADJ_IN( *line_in ) );
        snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out;
        int n;
        ++line_in;

        for ( n = chunk_count; n; --n )
        {
            /* order of input and output pixels must not be altered */
            SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
            SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
            SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );

            SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
            SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
            SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );

            SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
            SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
            SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
            SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );

            line_in  += 3;
            line_out += 7;
        }

        /* finish final pixels */
        SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
        SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
        SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );

        SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
        SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
        SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );

        SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
        SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
        SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
        SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );

        burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
        input += in_row_width;
        rgb_out = (char*) rgb_out + out_pitch;
    }
}

Может быть, мой параметр rgb_out все еще не распределяется должным образом, даже с моим [Out] на нем?Или, может быть, я неправильно рассчитываю некоторые размеры?Опять же, извиняюсь за огромное количество кода.

Ответы [ 2 ]

1 голос
/ 16 декабря 2010

Я думаю, что ваши проблемы возвращаются к вашему звонку в snes_ntsc_init.Я смоделировал это, создав неуправляемую DLL на C ++ (32-битную версию) и клиент C #, используя ваш код.В DLL функции snes_ntsc_init и snes_ntsc_blit ничего не делают - я просто ставлю на них точку останова, чтобы посмотреть, какие значения имеют маршалловые аргументы.

Это то, что я нашел в snes_ntsc_init:

ВторойПараметр «setup» является структурой *.Поэтому вы должны передать его как «ref setup» на стороне C #.

[DllImport("snes_server.dll")]
internal static extern
void snes_ntsc_init(snes_ntsc_t t, ref snes_ntsc_setup_t setup);

Я поместил атрибут MarshalAs на каждый элемент структуры в определении структуры C #, чтобы убедиться, что размеры были правильными.Моя DLL 32-битная, но ваша может быть 64-битной.Вот как я определил структуру в C #:

[StructLayout(LayoutKind.Sequential)]
internal struct snes_ntsc_setup_t
{
    [MarshalAs(UnmanagedType.R8)] public double hue;
    [MarshalAs(UnmanagedType.R8)] public double saturation;
    [MarshalAs(UnmanagedType.R8)] public double contrast;
    [MarshalAs(UnmanagedType.R8)] public double brightness;
    [MarshalAs(UnmanagedType.R8)] public double sharpness;

    [MarshalAs(UnmanagedType.R8)] public double gamma;
    [MarshalAs(UnmanagedType.R8)] public double resolution;
    [MarshalAs(UnmanagedType.R8)] public double artifacts;
    [MarshalAs(UnmanagedType.R8)] public double fringing;
    [MarshalAs(UnmanagedType.R8)] public double bleed;
    [MarshalAs(UnmanagedType.I4)] public int merge_fields;
    [MarshalAs(UnmanagedType.SysInt)] public IntPtr decoder_matrix;

    [MarshalAs(UnmanagedType.SysInt)] public IntPtr bsnes_colortbl;
}

decoder_matrix - это float *, а не float.(Либо опечатка, либо просто слепота кода с вашей стороны ... достаточно легко совершить эту ошибку.) Кроме того, поскольку это указатель, вам придется использовать небезопасный код для его инициализации или что-то в этом роде.В моем тесте я просто установил его в IntPtr.Zero.

Вот как я вызываю функцию из C #:

snes_ntsc_setup_t setup = new snes_ntsc_setup_t();
setup.merge_fields = 1;
setup.hue = 0.1;
setup.saturation = 0.2;
setup.contrast = 0.3;
setup.brightness = 0.4;
setup.sharpness = 0.5;
setup.gamma = 0.6;
setup.artifacts = 0.7;
setup.fringing = 0.8;
setup.bleed = 0.9;
setup.merge_fields = 10;
setup.decoder_matrix = IntPtr.Zero;
setup.bsnes_colortbl = IntPtr.Zero;
snes_ntsc_init(t, ref setup);

В отладчике, в DLL, я вижу, чтоаргумент "setup" содержит эти значения.Так что это означает, что все правильно.Не обязательно независимо от архитектуры, потому что там могут быть некоторые неявные предположения о размере, но, по крайней мере, это начало.

Я думаю, что преждевременно пытаться ответить на другие ваши вопросы.Я рекомендую вам создать заглушку DLL, как я сделал, и, по крайней мере, правильно распределить все аргументы.Тогда вы можете беспокоиться о семантике.

Удачи!И спасибо за интересный вопрос: -)

1 голос
/ 16 декабря 2010

В C unsigned long - это 32-разрядное целое число без знака - это соответствует uint в C #, а не ulong, что является 64-разрядным целым числом без знака. Поскольку эта структура передается по значению, вы, вероятно, передаете слишком много данных в функцию и забиваете остальные параметры данными изображения. Измените определение snes_ntsc_t в привязках C #, чтобы оно содержало uint[], и посмотрите, что произойдет.

(Вы совершили ту же ошибку в нескольких других местах. Вы можете просмотреть все свои определения для типов long и перепроверить тип C. AC long будет 32-разрядным и соответствовать * 1010 в C # *, в то время как C long long будет 64-битным и будет соответствовать C # long.)

...