开发者

C# marshalling image data - complex scenario

I found a library written in C++ that filters images with NTSC signal processing. You can see it here: http://slack.net/~ant/libs/ntsc.html#nes_ntsc The purpose is to make an image look like it was output by an NES onto a television.

I want to wrap the SNES version of the library in C# (actually, I would do the NES version but it only works with NTSC palette data, and not bitmaps.) After playing with the code for a few hours, I've admitted defeat, and come to you all for help.

Here is some of the C++ code in the library. I have added the 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 );

Here's a snippet from the C++ demo code. It uses 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 is a macro that returns 441 if you input 256.

Additionally, the library is built (by default) to work with 16 bits per pixel both in and out, in the 6 5 6 pattern.

Using all of this data, from the docs, defines, and typedefs, here is my attempt at some of the 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);

So, the problem is that on the snes_ntsc_blit line, I get an AccessViolationException. This wonderfully unhelpful error is basically a segfault, but I have no idea which of dozens of possible mistakes I have made:

  1. Is my snes_ntsc_t table allocated incorrectly? Maybe I should use the Marshal for it?
  2. Could my image size (602x448) be wrong? Would I still get this error if I made it too big, or would that be a possible safeguard to eliminate it as an error?
  3. Are my struct declarations correct? I don't know if maybe some more things need marshalling, if I have the wrong开发者_如何学编程 types, or some other things need to be out parameters.
  4. Are my blit parameters correct? I have no idea really but it seems to match what is being asked.
  5. Do I need to marshal the bitmap data somehow? If so please explain?

I'm sorry to post such a huge question, but marshalling is the bane of my C# career. I hate it. Please help.

Edit

I put breakpoints in and was able to step into the C code. My parameters for the init call are ok now, but for blit, my input param is all messed up. Mousing over it in VS (on the C side), shows what looks like an address and then some unicode garbage. I can't tell if this is ok or not. The error happens during an SNES_NTSC_RGB_OUT line of the inner for loop, but not on the first pass. Said macro does a bunch of math and then assigns to the second parameter. So here's the code of the blit function:

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;
    }
}

Maybe my rgb_out parameter still isn't marshalled properly, even with my [Out] on it? Or maybe I'm calculating some sizes wrong? Again, apologies for the enormous amount of code.


I think your problems go right back to your call to snes_ntsc_init. I mocked it up by making an unmanaged DLL in C++ (32-bit) and a C# client, using your code. In the DLL, the functions snes_ntsc_init and snes_ntsc_blit do nothing - I just put a breakpoint on them to see what values the marshalled arguments have.

This is what I found in snes_ntsc_init:

The second parameter, "setup", is a struct*. So you have to pass it as "ref setup" on the C# side.

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

I put a MarshalAs attribute on each struct member in the C# structure definition, to be sure that the sizes were correct. My DLL is 32-bit, but yours might be 64-bit. This is how I defined the struct in 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 is a float*, not a float. (Either a typo or just code blindness on your part... easy enough to make that mistake.) Also, since it's a pointer you'll have to use unsafe code to initialise it, or something like that. In my test, I just set it to IntPtr.Zero.

This is how I call the function from 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);

In the debugger, in the DLL, I can see that the "setup" argument contains these values. So that means it's marshalled correctly. Not necessarily architecture-independently, because there might still be some implicit size assumptions in there, but at least it's a start.

I think it's premature to attempt to answer your other questions. I recommend that you build a stub DLL like I did and at least get all your arguments marshalling correctly. Then you can worry about the semantics.

Good luck! And thanks for the interesting question :-)


In C, unsigned long is a 32-bit unsigned integer -- this corresponds to uint in C#, not ulong, which is a 64-bit unsigned integer. Since this struct is passed by value, you're probably passing way too much data to the function and clobbering the rest of the parameters with image data. Change your definition of snes_ntsc_t in the C# bindings to contain a uint[] and see what happens.

(You have made the same mistake a few other places. You might review all of your definitions for long types and double-check the C type. A C long will be 32-bit and correspond to C#'s int, while a C long long will be 64-bit and correspond to C#'s long.)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜