How can I pattern match against System.Drawing.Imaging ImageFormats?

I’m loading in an image from file and doing some checks to see if I can use it later but I can’t seem to get the pattern match to work when I am validating the type of image.

I’m loading the image via:

open System
open System.Drawing
open System.Drawing.Imaging

type bitmapLoader = string -> Bitmap

let loadBitmap : bitmapLoader =
        fun filename -> Bitmap.FromFile(filename) :?> Bitmap

The details of the image are being copied into:

type BitmapDetails =
    { Height : int 
      VerticalResolution : float 
      Width : int 
      HorizontalResolution : float
      PixelFormat : PixelFormat
      RawFormat : ImageFormat 
      Flags : ImageFlags
      }

And then I’m trying to only allow certain type and formats with something like:

let isInvalidBitmapType (bitmapDetails : BitmapDetails) =
        let jpeg = ImageFormat.Jpeg
        let png = ImageFormat.Png
        match bitmapDetails.RawFormat, bitmapDetails.PixelFormat with 
        | jpeg , PixelFormat.Format24bppRgb -> false
        | png , PixelFormat.Format24bppRgb -> false
        | png , PixelFormat.Format32bppArgb -> false
        | _ , _ -> true

But it’s not working – jpeg is not used and second rule will not be matched.

I’ve tried all kinds of different versions of the above function but they all have warning messages of various types:

let isInvalidBitmapType (bitmapDetails : BitmapDetails) =
        match bitmapDetails.RawFormat, bitmapDetails.PixelFormat with 
        | :? System.Drawing.Imaging.ImageFormat.Jpeg , PixelFormat.Format24bppRgb -> false
        | :? System.Drawing.Imaging.ImageFormat.Png , PixelFormat.Format24bppRgb -> false
        | :? System.Drawing.Imaging.ImageFormat.Png , PixelFormat.Format32bppArgb -> false
        | _ , _ -> true

let isInvalidBitmapType (bitmapDetails : BitmapDetails) =
        let jpeg = ImageFormat.Jpeg
        let png = ImageFormat.Png
        match bitmapDetails.RawFormat, bitmapDetails.PixelFormat with 
        | :? jpeg , PixelFormat.Format24bppRgb -> false
        | :? png , PixelFormat.Format24bppRgb -> false
        | :? png , PixelFormat.Format32bppArgb -> false
        | _ , _ -> true

let isInvalidBitmapType (bitmapDetails : BitmapDetails) =
        let jpeg = ImageFormat.Jpeg.GetType()
        let png = ImageFormat.Png.GetType()
        match bitmapDetails.RawFormat.GetType(), bitmapDetails.PixelFormat with 
        | :? jpeg , PixelFormat.Format24bppRgb -> false
        | :? png , PixelFormat.Format24bppRgb -> false
        | :? png , PixelFormat.Format32bppArgb -> false
        | _ , _ -> true

And so many others that it just seems like a futile/frustrating exercise now.

Can anyone help, and explain what I am doing wrong?

I think I’ve managed to get round the problem by using active patterns:

    let (|IsJpeg|IsPng|InvalidRawFormat|) format =
        if format = ImageFormat.Jpeg then IsJpeg
        else if format = ImageFormat.Png then IsPng
        else InvalidRawFormat

    let (|IsEightBitNoTransparency|IsEightBitWithTransparency|InvalidPixelFormat|) format =
        if format = PixelFormat.Format24bppRgb then IsEightBitNoTransparency 
        else if format = PixelFormat.Format32bppArgb then IsEightBitWithTransparency 
        else InvalidPixelFormat

    let isInvalidImageFormat rawFormat pixelFormat =
        match rawFormat, pixelFormat with 
        | IsJpeg, IsEightBitNoTransparency // Jpegs can't (normally) have transparency.
        | IsPng, IsEightBitNoTransparency
        | IsPng, IsEightBitWithTransparency -> false
        | _ , _ -> true

…but it would be nice to know if my original code could be ‘tweaked’ to work somehow.

let isInvalidBitmapType (bitmapDetails : BitmapDetails) =
        let jpeg = ImageFormat.Jpeg
        let png = ImageFormat.Png
        match bitmapDetails.RawFormat, bitmapDetails.PixelFormat with 
        | rf, PixelFormat.Format24bppRgb when rf = jpeg -> false
        | rf, PixelFormat.Format24bppRgb when rf = png -> false
        | rf, PixelFormat.Format32bppArgb when rf = png -> false
        | _ , _ -> true

The problem is that the ‘jpeg’ in this line:

        | jpeg , PixelFormat.Format24bppRgb -> false

Is not comparing the raw format to the jpeg value you introduced in the let binding, it is binding the name jpeg to whatever you are matching on. The two ‘jpeg’ names then have nothing to do with each other. When you use a name in a pattern match like that is matches any input value and binds it to the name you give. If jpeg were a constant like an enum value (like the PixelFormat values) it would do as you expect, but it’s just a reference in this case and so gets shadowed by the new jpeg value. You could then use that second jpeg value in the expression where you are just evaluating false.

Thanks for that reply, it makes more sense to me now – I was getting myself all mixed up with all of the different versions I tried.

I’ve now changed things round a bit to use some DUs (and your suggestion):

and ImageType = | Bmp | Emf | Exif | Gif | Heif | Icon | Jpeg | Png | Tiff | Webp | Wmf | UnhandledImageType 
and PixelConstruction =
    | EightBitNoTransparency
    | EightBitWithTransparency
    | UnhandledPixelConstruction

let getImageType format =
    match format with 
    | fmt when fmt = ImageFormat.Jpeg -> Jpeg 
    | fmt when fmt = ImageFormat.Png -> Png 
    | _ -> UnhandledImageType

let getPixelConstruction format = 
    match format with 
    | PixelFormat.Format24bppRgb -> EightBitNoTransparency
    | PixelFormat.Format32bppArgb -> EightBitWithTransparency
    | _ -> UnhandledPixelConstruction

let isInvalidImageFormat imageType pixelConstruction =
        match imageType, pixelConstruction with 
        | Jpeg, EightBitNoTransparency // Jpegs can't have transparency.
        | Png, EightBitNoTransparency
        | Png, EightBitWithTransparency -> false
        | _ , _ -> true

Just out of interest, when might I use the :? in a match expression?
I’ve used it in a try...with but I’m wondering how useful it might be elsewhere.
Or should I forget about it until I find that I really need it?

I searched my largest F# project and found only one place where a type pattern match was used for something other than exception hierarchies. F# runs on .Net and some of that API predates generics or wraps around system API’s that do some odd things here and there. In the one case I used it, it was for reading from a thread named data slot, which gives you an ‘Object’ back and then you need to handle it based on the actual type. A type pattern with a when guard was ideal. So it’s something handy to have but not frequently except for the exceptions.

Thanks for the extra information.
I feel better about leaving that sort of thing until I really need it now.