Skip to content

[Feat]: Adding screenshot feature #63

Merged
mdales merged 23 commits into
claudiusFX:mainfrom
pawaskar-shreya:feat/screenshot
Apr 12, 2025
Merged

[Feat]: Adding screenshot feature #63
mdales merged 23 commits into
claudiusFX:mainfrom
pawaskar-shreya:feat/screenshot

Conversation

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator

@pawaskar-shreya pawaskar-shreya commented Apr 6, 2025

Attempting to fix #39

To be able to use the ocaml-gif for screenshot feature, I have vendored it into Claudius.

I have added screenshot modules for implementing the feature. To ensure uniqueness for every screenshot saved, I am using the name for screenshot in the format "screenshot_ddmmyy_hhmmss"

The current pr has a lot of debug statements and I will clean those up after getting a review from you @mdales, but currently I am getting an index out of bounds error. Can you please help me with why I am getting this error.

image

Also, on running dune runtest, I am getting these other bunch of error:

image

@mdales
Copy link
Copy Markdown
Collaborator

mdales commented Apr 7, 2025

Unfortunately you're hitting a bug in ocaml-gif. You can see this more clearly if you run the following:

export OCAMLRUNPARAM=b

Which gives you a stack trace. I'm not sure what the problem is, whether its a logic bug in the LZW compression (most likely) or a lack of sanity check on the inputs to say they're not quite what is expected. I did try running your test with a 256 colour palette, given that is probably a better tested option, and we still get the same error.

Looking at the ocaml-gif code, which I've not looked at for six months, so I'm a bit rust, but I suspect we might need to first compact the data so that the all the 6 bit values are just taking 6 bits (i.e., call Lzw.flatten_codes, which you can see in the test_lzw.ml in test_encode_decode for instance). So even though with a 64 bit palette we need 6 bits per pixel, you're passing to encode much more data than it expects: for 10 pixels you'll pass 10 bytes (or 80 bits), but it'll expect 60 bits (8 bytes rounded up). But the documentation on ocaml-gif is clearly non-existant, and that's entirely my fault!

I also fear that there's another issue. Currently the test_encode_decode tests compressing and decompressing data with 8, 4, and 2 bits per pixel, and I added 6 to that, which is a valid code size, and the tests fail. I created an issue for that: claudiusFX/ocaml-gif#1

So, a bit of a mess: the ocaml-gif writing has clearly not been as well tested as I thought. When I updated the library back in August I mostly was focussed on the read side of things, and just did a few tests for write, but clearly not enough.

So, what are the next steps here:

  • Can you file an issue on ocaml-gif, with a link to this PR, as there is clearly a bug here in ocaml-gif

Then you have two options:

  1. You can see if you can work out what's going wrong in the LZW compression code in ocaml-gif
  2. Move to the other graphics library you found and used that instead

My heart says you should do the first option, but my head says you should do the second option :) Whilst I'd love to see you fix ocaml-gif, there's a lot to get your head around in the LZW code, and only a week left in the Outreachy contribution phase, and so I think it best if for now you switch to the other library which is probably going to actually work out the box and let you get this PR working this week. This means the infrastructure around screenshots will be in place and we'll have something working, which is better than running out of time and having just a broken PR.

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

pawaskar-shreya commented Apr 7, 2025

Thank you soooo much for the detailed explanation. That really helps make sense of what’s going on. I actually really appreciate the chance to learn more about the internals here, even if it might be a bit challenging.

Even my heart says to go with the first option. So, I think I’d like to stick with ocaml-gif for now and see if I can figure out what’s going wrong in the LZW compression. It’ll be a bit of a challenge, but it sounds like a great learning opportunity, and I’d love to try and contribute a fix if I can under your guidance.

I’ll definitely go and open the issue on the ocaml-gif repo and link this PR as you suggested. If I get completely stuck or time starts running too short, then we can pivot to the other library — but for now, I’m excited to see if I can make this work.

Thanks again for your support @mdales! It means a lot! I've learnt a lot of things in the past couple of weeks. From being completely new to the language to being able to make contributions to Claudius. It has been an amazing for sure!

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

pawaskar-shreya commented Apr 7, 2025

Actually I've been also trying to understand ocaml-gif from the last couple of days. I'd be very happy if we can figure out this!

@mdales
Copy link
Copy Markdown
Collaborator

mdales commented Apr 7, 2025

Okay, good luck! Do remember though that you'll need to submit your contributions https://www.outreachy.org/outreachy-june-2025-internship-cohort/communities/ocaml/claudius/contributions/ here by the 15th of April, and have your application with a proposed timeline for your project submitted here also: https://www.outreachy.org/eligibility/ - whilst I like that you want to follow the problem, just don't lose track of that deadline!

@mdales
Copy link
Copy Markdown
Collaborator

mdales commented Apr 8, 2025

As noted, I fixed the original issue causing the out of bounds, which was just a silly mistake on my behalf I think. However, the GIF files saved are still not valid for some reason, so there is more work to do here!

One thing to note in your test, you do need to pack the 6 bit data before encoding it (I think :) currently your test is really compressing 8 bit data where the top two bits are just always 0. Again, this is down to my fault for not documenting the GIF library properly ahead of making the original ticket, apologies. I think you want something like this:

let save_screenshot (fb : Framebuffer.t) (palette : Palette.t) =
  let width = Array.length fb.data.(0) in
  let height = Array.length fb.data in
  Printf.printf "Framebuffer dimensions: %d x %d\n" width height;   (*Debug*)
  let size = width * height in

  let colors = palette
    |> color_table_of_palette
    |> pad_palette_to_power_of_two
  in

  let color_depth =
    let len = Array.length colors in
    let rec bits_needed n b = if n <= 1 then b else bits_needed (n / 2) (b + 1) in
    min 8 (max 2 (bits_needed (len-1) 1))
  in

  let nulls_replaced = ref 0 in
  let pixels =
    List.init size (fun idx ->
      let x = idx mod width in
      let y = idx / width in
      let v = fb.data.(y).(x) in
      if v < 0 || v > 255 then
        failwith (Printf.sprintf "Framebuffer value %d out of byte range at (%d,%d)" v x y);
      if v = 0 then incr nulls_replaced;
      let max_index = Palette.size palette - 1 in
      let safe_v =
        if v = 0 then 1
        else if v > max_index then max_index
        else v in
      (Z.of_int safe_v, color_depth)
    )
  in
  Printf.printf "Replaced %d null pixels with index 1\n%!" !nulls_replaced;

  let palette_len = Array.length colors in
  let max_allowed_color_depth =
    let rec bits_needed n b = if n <= 1 then b else bits_needed (n / 2) (b + 1) in
    bits_needed (palette_len - 1) 1
  in
  if color_depth > max_allowed_color_depth then
    failwith (Printf.sprintf
      "Color depth %d exceeds max allowed for palette size %d (max: %d)"
      color_depth palette_len max_allowed_color_depth);

  Printf.printf "Color depth: %d\n" color_depth;   (*Debug*)
  Printf.printf "Pixels length: %d, expected: %d\n" (List.length pixels) (width * height);

  Printf.printf "Palette size: %d, color depth: %d\n" (Array.length colors) color_depth;
  let flattened = Lzw.flatten_codes color_depth pixels in
  Printf.printf "Uncommpressed size: %d, flattened size: %d\n" (List.length pixels) (Bytes.length flattened);
  let compressed = Lzw.encode flattened color_depth in
  Printf.printf "Uncompressed size: %d, Compressed size: %d\n%!" (List.length pixels) (Bytes.length compressed);

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

I am so sorry for this @mdales

Even I was myself trying to find out what was going wrong and try to find a fix for it. Really sorry for taking this much time, but understanding the entire compression process and how it is working was really taking me alot of time to fully understand it deeply as it was very new for me. I really feel bad as I was not able to make this on time :(

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

One thing to note in your test, you do need to pack the 6 bit data before encoding it (I think :) currently your test is really compressing 8 bit data where the top two bits are just always 0.

Yes, exactly. I was trying all possible ways to find out why the index out of bounds error was arising. It was sort of in that direction to find if things work in that way.

Again, this is down to my fault for not documenting the GIF library properly ahead of making the original ticket, apologies.

But even though it was not documented, it was really fun trying to understand it.

Thankyou very much for taking out time and sovling this error as I probabily would have needed a little bit of more time to do it on my own and as the deadlines are approaching, I really appreciate your help here!

I will fix the remaining issues in the pr and clean up all the debug statements and report back to you!

Thank you!

@mdales
Copy link
Copy Markdown
Collaborator

mdales commented Apr 8, 2025

@pawaskar-shreya no need to apologise - I should have tested ocaml-gif more before suggesting it as an approach.

The GIF's work for 256 colours:

test

I had to convert that with imagemagick, as Preview on the Mac didn't like the image, but imagemagick seems to like it fine:

Image:
  Filename: ./_build/default/test/screenshot_080425_132236.gif
  Permissions: rw-r--r--
  Format: GIF (CompuServe graphics interchange format)
  Mime type: image/gif
  Class: PseudoClass
  Geometry: 100x112+0+0
  Units: Undefined
  Colorspace: sRGB
  Type: Palette
  Base type: Undefined
  Endianness: Undefined
  Depth: 8-bit
  Channels: 4.0
  Channel depth:
    Red: 8-bit
    Green: 8-bit
    Blue: 8-bit
  Channel statistics:
    Pixels: 11200
    Red:
      min: 127  (0.498039)
      max: 128 (0.501961)
      mean: 127.01 (0.498077)
      median: 127 (0.498039)
      standard deviation: 0.0977278 (0.000383246)
      kurtosis: 98.6953
      skewness: 10.0343
      entropy: 0.0784161
    Green:
      min: 59  (0.231373)
      max: 207 (0.811765)
      mean: 131.939 (0.517408)
      median: 132 (0.517647)
      standard deviation: 42.7628 (0.167697)
      kurtosis: -1.19525
      skewness: 0.0435052
      entropy: 0.977815
    Blue:
      min: 143  (0.560784)
      max: 207 (0.811765)
      mean: 174.144 (0.682916)
      median: 174 (0.682353)
      standard deviation: 18.428 (0.0722666)
      kurtosis: -1.19219
      skewness: 0.0458403
      entropy: 0.996152
  Image statistics:
    Overall:
      min: 59  (0.231373)
      max: 207 (0.811765)
      mean: 144.364 (0.566134)
      median: 144.333 (0.566013)
      standard deviation: 20.4295 (0.0801157)
      kurtosis: 32.1026
      skewness: 3.37454
      entropy: 0.684128
  Colors: 175
  Histogram:
            64: (127,59,143) #7F3B8F srgb(127,59,143)
            86: (127,60,143) #7F3C8F srgb(127,60,143)
            67: (127,61,144) #7F3D90 srgb(127,61,144)
            15: (127,62,144) #7F3E90 srgb(127,62,144)
            91: (127,63,144) #7F3F90 srgb(127,63,144)
            18: (127,63,145) #7F3F91 srgb(127,63,145)
            70: (127,64,145) #7F4091 srgb(127,64,145)
            66: (127,65,145) #7F4191 srgb(127,65,145)
            25: (127,65,146) #7F4192 srgb(127,65,146)
            49: (127,66,146) #7F4292 srgb(127,66,146)
           141: (127,67,146) #7F4392 srgb(127,67,146)
            59: (127,68,147) #7F4493 srgb(127,68,147)
            22: (127,69,147) #7F4593 srgb(127,69,147)
            71: (127,70,147) #7F4693 srgb(127,70,147)
            26: (127,70,148) #7F4694 srgb(127,70,148)
            57: (127,71,148) #7F4794 srgb(127,71,148)
            95: (127,72,148) #7F4894 srgb(127,72,148)
            18: (127,72,149) #7F4895 srgb(127,72,149)
            45: (127,73,149) #7F4995 srgb(127,73,149)
            94: (127,74,149) #7F4A95 srgb(127,74,149)
            73: (127,75,150) #7F4B96 srgb(127,75,150)
           174: (127,76,150) #7F4C96 srgb(127,76,150)
            24: (127,77,151) #7F4D97 srgb(127,77,151)
            68: (127,78,151) #7F4E97 srgb(127,78,151)
            62: (127,79,151) #7F4F97 srgb(127,79,151)
            19: (127,79,152) #7F4F98 srgb(127,79,152)
            50: (127,80,152) #7F5098 srgb(127,80,152)
           119: (127,81,152) #7F5198 srgb(127,81,152)
            73: (127,82,153) #7F5299 srgb(127,82,153)
            93: (127,83,153) #7F5399 srgb(127,83,153)
            28: (127,84,154) #7F549A srgb(127,84,154)
            62: (127,85,154) #7F559A srgb(127,85,154)
           115: (127,86,154) #7F569A srgb(127,86,154)
            17: (127,86,155) #7F569B srgb(127,86,155)
            44: (127,87,155) #7F579B srgb(127,87,155)
            93: (127,88,155) #7F589B srgb(127,88,155)
            67: (127,89,156) #7F599C srgb(127,89,156)
           119: (127,90,156) #7F5A9C srgb(127,90,156)
            19: (127,91,157) #7F5B9D srgb(127,91,157)
            68: (127,92,157) #7F5C9D srgb(127,92,157)
            74: (127,93,157) #7F5D9D srgb(127,93,157)
            20: (127,93,158) #7F5D9E srgb(127,93,158)
            73: (127,94,158) #7F5E9E srgb(127,94,158)
           174: (127,95,158) #7F5F9E srgb(127,95,158)
            71: (127,96,159) #7F609F srgb(127,96,159)
            92: (127,97,159) #7F619F srgb(127,97,159)
            27: (127,98,160) #7F62A0 srgb(127,98,160)
            62: (127,99,160) #7F63A0 srgb(127,99,160)
            91: (127,100,160) #7F64A0 srgb(127,100,160)
            26: (127,100,161) #7F64A1 srgb(127,100,161)
            67: (127,101,161) #7F65A1 srgb(127,101,161)
            65: (127,102,161) #7F66A1 srgb(127,102,161)
            69: (127,103,162) #7F67A2 srgb(127,103,162)
           130: (127,104,162) #7F68A2 srgb(127,104,162)
            21: (127,105,163) #7F69A3 srgb(127,105,163)
            70: (127,106,163) #7F6AA3 srgb(127,106,163)
            71: (127,107,163) #7F6BA3 srgb(127,107,163)
            23: (127,107,164) #7F6BA4 srgb(127,107,164)
            71: (127,108,164) #7F6CA4 srgb(127,108,164)
            87: (127,109,164) #7F6DA4 srgb(127,109,164)
            79: (127,110,165) #7F6EA5 srgb(127,110,165)
            82: (127,111,165) #7F6FA5 srgb(127,111,165)
            64: (127,112,166) #7F70A6 srgb(127,112,166)
            24: (127,113,166) #7F71A6 srgb(127,113,166)
           145: (127,114,166) #7F72A6 srgb(127,114,166)
            29: (127,114,167) #7F72A7 srgb(127,114,167)
            74: (127,115,167) #7F73A7 srgb(127,115,167)
            69: (127,116,167) #7F74A7 srgb(127,116,167)
            63: (127,117,168) #7F75A8 srgb(127,117,168)
           106: (127,118,168) #7F76A8 srgb(127,118,168)
            58: (127,119,169) #7F77A9 srgb(127,119,169)
            23: (127,120,169) #7F78A9 srgb(127,120,169)
            71: (127,121,169) #7F79A9 srgb(127,121,169)
            20: (127,121,170) #7F79AA srgb(127,121,170)
            58: (127,122,170) #7F7AAA srgb(127,122,170)
           111: (127,123,170) #7F7BAA srgb(127,123,170)
            75: (127,124,171) #7F7CAB srgb(127,124,171)
            87: (127,125,171) #7F7DAB srgb(127,125,171)
            69: (127,126,172) #7F7EAC srgb(127,126,172)
            17: (127,127,172) #7F7FAC srgb(127,127,172)
            90: (127,128,172) #7F80AC srgb(127,128,172)
            14: (127,128,173) #7F80AD srgb(127,128,173)
            65: (127,129,173) #7F81AD srgb(127,129,173)
            62: (127,130,173) #7F82AD srgb(127,130,173)
            20: (127,130,174) #7F82AE srgb(127,130,174)
            46: (127,131,174) #7F83AE srgb(127,131,174)
           166: (127,132,174) #7F84AE srgb(127,132,174)
            68: (127,133,175) #7F85AF srgb(127,133,175)
            23: (127,134,175) #7F86AF srgb(127,134,175)
            71: (127,135,175) #7F87AF srgb(127,135,175)
            20: (127,135,176) #7F87B0 srgb(127,135,176)
            79: (127,136,176) #7F88B0 srgb(127,136,176)
            84: (127,137,176) #7F89B0 srgb(127,137,176)
            27: (127,137,177) #7F89B1 srgb(127,137,177)
            43: (127,138,177) #7F8AB1 srgb(127,138,177)
            92: (127,139,177) #7F8BB1 srgb(127,139,177)
            60: (127,140,178) #7F8CB2 srgb(127,140,178)
           131: (127,141,178) #7F8DB2 srgb(127,141,178)
            28: (127,142,179) #7F8EB3 srgb(127,142,179)
            69: (127,143,179) #7F8FB3 srgb(127,143,179)
            63: (127,144,179) #7F90B3 srgb(127,144,179)
            20: (127,144,180) #7F90B4 srgb(127,144,180)
            49: (127,145,180) #7F91B4 srgb(127,145,180)
           109: (127,146,180) #7F92B4 srgb(127,146,180)
            72: (127,147,181) #7F93B5 srgb(127,147,181)
            86: (127,148,181) #7F94B5 srgb(127,148,181)
            19: (127,149,182) #7F95B6 srgb(127,149,182)
            56: (127,150,182) #7F96B6 srgb(127,150,182)
           141: (127,151,182) #7F97B6 srgb(127,151,182)
            21: (127,151,183) #7F97B7 srgb(127,151,183)
            42: (127,152,183) #7F98B7 srgb(127,152,183)
            93: (127,153,183) #7F99B7 srgb(127,153,183)
            61: (127,154,184) #7F9AB8 srgb(127,154,184)
           102: (127,155,184) #7F9BB8 srgb(127,155,184)
            19: (127,156,185) #7F9CB9 srgb(127,156,185)
            63: (127,157,185) #7F9DB9 srgb(127,157,185)
            63: (127,158,185) #7F9EB9 srgb(127,158,185)
            18: (127,158,186) #7F9EBA srgb(127,158,186)
            66: (127,159,186) #7F9FBA srgb(127,159,186)
           109: (127,160,186) #7FA0BA srgb(127,160,186)
            69: (127,161,187) #7FA1BB srgb(127,161,187)
            86: (127,162,187) #7FA2BB srgb(127,162,187)
            23: (127,163,188) #7FA3BC srgb(127,163,188)
            59: (127,164,188) #7FA4BC srgb(127,164,188)
            84: (127,165,188) #7FA5BC srgb(127,165,188)
            24: (127,165,189) #7FA5BD srgb(127,165,189)
            60: (127,166,189) #7FA6BD srgb(127,166,189)
            61: (127,167,189) #7FA7BD srgb(127,167,189)
            67: (127,168,190) #7FA8BE srgb(127,168,190)
           188: (127,169,190) #7FA9BE srgb(127,169,190)
            14: (127,170,191) #7FAABF srgb(127,170,191)
            71: (127,171,191) #7FABBF srgb(127,171,191)
            59: (127,172,191) #7FACBF srgb(127,172,191)
            17: (127,172,192) #7FACC0 srgb(127,172,192)
            70: (127,173,192) #7FADC0 srgb(127,173,192)
            85: (127,174,192) #7FAEC0 srgb(127,174,192)
            56: (127,175,193) #7FAFC1 srgb(127,175,193)
            88: (127,176,193) #7FB0C1 srgb(127,176,193)
            62: (127,177,194) #7FB1C2 srgb(127,177,194)
            27: (127,178,194) #7FB2C2 srgb(127,178,194)
           109: (127,179,194) #7FB3C2 srgb(127,179,194)
            24: (127,179,195) #7FB3C3 srgb(127,179,195)
            60: (127,180,195) #7FB4C3 srgb(127,180,195)
            62: (127,181,195) #7FB5C3 srgb(127,181,195)
            57: (127,182,196) #7FB6C4 srgb(127,182,196)
           105: (127,183,196) #7FB7C4 srgb(127,183,196)
            53: (127,184,197) #7FB8C5 srgb(127,184,197)
            23: (127,185,197) #7FB9C5 srgb(127,185,197)
            70: (127,186,197) #7FBAC5 srgb(127,186,197)
            22: (127,186,198) #7FBAC6 srgb(127,186,198)
            62: (127,187,198) #7FBBC6 srgb(127,187,198)
           140: (127,188,198) #7FBCC6 srgb(127,188,198)
            52: (127,189,199) #7FBDC7 srgb(127,189,199)
            82: (127,190,199) #7FBEC7 srgb(127,190,199)
            65: (127,191,200) #7FBFC8 srgb(127,191,200)
            27: (127,192,200) #7FC0C8 srgb(127,192,200)
            82: (127,193,200) #7FC1C8 srgb(127,193,200)
            27: (127,193,201) #7FC1C9 srgb(127,193,201)
            65: (127,194,201) #7FC2C9 srgb(127,194,201)
            62: (127,195,201) #7FC3C9 srgb(127,195,201)
            22: (127,195,202) #7FC3CA srgb(127,195,202)
            44: (127,196,202) #7FC4CA srgb(127,196,202)
           139: (127,197,202) #7FC5CA srgb(127,197,202)
            53: (127,198,203) #7FC6CB srgb(127,198,203)
            20: (127,199,203) #7FC7CB srgb(127,199,203)
            65: (127,200,203) #7FC8CB srgb(127,200,203)
            21: (127,200,204) #7FC8CC srgb(127,200,204)
            66: (127,201,204) #7FC9CC srgb(127,201,204)
            84: (127,202,204) #7FCACC srgb(127,202,204)
            29: (127,202,205) #7FCACD srgb(127,202,205)
            44: (127,203,205) #7FCBCD srgb(127,203,205)
            87: (127,204,205) #7FCCCD srgb(127,204,205)
            66: (127,205,206) #7FCDCE srgb(127,205,206)
            29: (127,206,206) #7FCECE srgb(127,206,206)
           108: (128,207,207) #80CFCF srgb(128,207,207)
  Colormap entries: 256
  Colormap:
    0: (127,59,143,1) #7F3B8FFF srgba(127,59,143,1)
    1: (127,59,143,1) #7F3B8FFF srgba(127,59,143,1)
    2: (127,60,143,1) #7F3C8FFF srgba(127,60,143,1)
    3: (127,60,143,1) #7F3C8FFF srgba(127,60,143,1)
    4: (127,61,144,1) #7F3D90FF srgba(127,61,144,1)
    5: (127,61,144,1) #7F3D90FF srgba(127,61,144,1)
    6: (127,62,144,1) #7F3E90FF srgba(127,62,144,1)
    7: (127,63,144,1) #7F3F90FF srgba(127,63,144,1)
    8: (127,63,145,1) #7F3F91FF srgba(127,63,145,1)
    9: (127,64,145,1) #7F4091FF srgba(127,64,145,1)
    10: (127,64,145,1) #7F4091FF srgba(127,64,145,1)
    11: (127,65,145,1) #7F4191FF srgba(127,65,145,1)
    12: (127,65,146,1) #7F4192FF srgba(127,65,146,1)
    13: (127,66,146,1) #7F4292FF srgba(127,66,146,1)
    14: (127,67,146,1) #7F4392FF srgba(127,67,146,1)
    15: (127,67,146,1) #7F4392FF srgba(127,67,146,1)
    16: (127,68,147,1) #7F4493FF srgba(127,68,147,1)
    17: (127,68,147,1) #7F4493FF srgba(127,68,147,1)
    18: (127,69,147,1) #7F4593FF srgba(127,69,147,1)
    19: (127,70,147,1) #7F4693FF srgba(127,70,147,1)
    20: (127,70,148,1) #7F4694FF srgba(127,70,148,1)
    21: (127,71,148,1) #7F4794FF srgba(127,71,148,1)
    22: (127,71,148,1) #7F4794FF srgba(127,71,148,1)
    23: (127,72,148,1) #7F4894FF srgba(127,72,148,1)
    24: (127,72,149,1) #7F4895FF srgba(127,72,149,1)
    25: (127,73,149,1) #7F4995FF srgba(127,73,149,1)
    26: (127,74,149,1) #7F4A95FF srgba(127,74,149,1)
    27: (127,74,149,1) #7F4A95FF srgba(127,74,149,1)
    28: (127,75,150,1) #7F4B96FF srgba(127,75,150,1)
    29: (127,75,150,1) #7F4B96FF srgba(127,75,150,1)
    30: (127,76,150,1) #7F4C96FF srgba(127,76,150,1)
    31: (127,76,150,1) #7F4C96FF srgba(127,76,150,1)
    32: (127,77,151,1) #7F4D97FF srgba(127,77,151,1)
    33: (127,78,151,1) #7F4E97FF srgba(127,78,151,1)
    34: (127,78,151,1) #7F4E97FF srgba(127,78,151,1)
    35: (127,79,151,1) #7F4F97FF srgba(127,79,151,1)
    36: (127,79,152,1) #7F4F98FF srgba(127,79,152,1)
    37: (127,80,152,1) #7F5098FF srgba(127,80,152,1)
    38: (127,81,152,1) #7F5198FF srgba(127,81,152,1)
    39: (127,81,152,1) #7F5198FF srgba(127,81,152,1)
    40: (127,82,153,1) #7F5299FF srgba(127,82,153,1)
    41: (127,82,153,1) #7F5299FF srgba(127,82,153,1)
    42: (127,83,153,1) #7F5399FF srgba(127,83,153,1)
    43: (127,83,153,1) #7F5399FF srgba(127,83,153,1)
    44: (127,84,154,1) #7F549AFF srgba(127,84,154,1)
    45: (127,85,154,1) #7F559AFF srgba(127,85,154,1)
    46: (127,85,154,1) #7F559AFF srgba(127,85,154,1)
    47: (127,86,154,1) #7F569AFF srgba(127,86,154,1)
    48: (127,86,155,1) #7F569BFF srgba(127,86,155,1)
    49: (127,87,155,1) #7F579BFF srgba(127,87,155,1)
    50: (127,88,155,1) #7F589BFF srgba(127,88,155,1)
    51: (127,88,155,1) #7F589BFF srgba(127,88,155,1)
    52: (127,89,156,1) #7F599CFF srgba(127,89,156,1)
    53: (127,89,156,1) #7F599CFF srgba(127,89,156,1)
    54: (127,90,156,1) #7F5A9CFF srgba(127,90,156,1)
    55: (127,90,156,1) #7F5A9CFF srgba(127,90,156,1)
    56: (127,91,157,1) #7F5B9DFF srgba(127,91,157,1)
    57: (127,92,157,1) #7F5C9DFF srgba(127,92,157,1)
    58: (127,92,157,1) #7F5C9DFF srgba(127,92,157,1)
    59: (127,93,157,1) #7F5D9DFF srgba(127,93,157,1)
    60: (127,93,158,1) #7F5D9EFF srgba(127,93,158,1)
    61: (127,94,158,1) #7F5E9EFF srgba(127,94,158,1)
    62: (127,94,158,1) #7F5E9EFF srgba(127,94,158,1)
    63: (127,95,158,1) #7F5F9EFF srgba(127,95,158,1)
    64: (127,96,159,1) #7F609FFF srgba(127,96,159,1)
    65: (127,96,159,1) #7F609FFF srgba(127,96,159,1)
    66: (127,97,159,1) #7F619FFF srgba(127,97,159,1)
    67: (127,97,159,1) #7F619FFF srgba(127,97,159,1)
    68: (127,98,160,1) #7F62A0FF srgba(127,98,160,1)
    69: (127,99,160,1) #7F63A0FF srgba(127,99,160,1)
    70: (127,99,160,1) #7F63A0FF srgba(127,99,160,1)
    71: (127,100,160,1) #7F64A0FF srgba(127,100,160,1)
    72: (127,100,161,1) #7F64A1FF srgba(127,100,161,1)
    73: (127,101,161,1) #7F65A1FF srgba(127,101,161,1)
    74: (127,101,161,1) #7F65A1FF srgba(127,101,161,1)
    75: (127,102,161,1) #7F66A1FF srgba(127,102,161,1)
    76: (127,103,162,1) #7F67A2FF srgba(127,103,162,1)
    77: (127,103,162,1) #7F67A2FF srgba(127,103,162,1)
    78: (127,104,162,1) #7F68A2FF srgba(127,104,162,1)
    79: (127,104,162,1) #7F68A2FF srgba(127,104,162,1)
    80: (127,105,163,1) #7F69A3FF srgba(127,105,163,1)
    81: (127,106,163,1) #7F6AA3FF srgba(127,106,163,1)
    82: (127,106,163,1) #7F6AA3FF srgba(127,106,163,1)
    83: (127,107,163,1) #7F6BA3FF srgba(127,107,163,1)
    84: (127,107,164,1) #7F6BA4FF srgba(127,107,164,1)
    85: (127,108,164,1) #7F6CA4FF srgba(127,108,164,1)
    86: (127,108,164,1) #7F6CA4FF srgba(127,108,164,1)
    87: (127,109,164,1) #7F6DA4FF srgba(127,109,164,1)
    88: (127,110,165,1) #7F6EA5FF srgba(127,110,165,1)
    89: (127,110,165,1) #7F6EA5FF srgba(127,110,165,1)
    90: (127,111,165,1) #7F6FA5FF srgba(127,111,165,1)
    91: (127,111,165,1) #7F6FA5FF srgba(127,111,165,1)
    92: (127,112,166,1) #7F70A6FF srgba(127,112,166,1)
    93: (127,112,166,1) #7F70A6FF srgba(127,112,166,1)
    94: (127,113,166,1) #7F71A6FF srgba(127,113,166,1)
    95: (127,114,166,1) #7F72A6FF srgba(127,114,166,1)
    96: (127,114,167,1) #7F72A7FF srgba(127,114,167,1)
    97: (127,115,167,1) #7F73A7FF srgba(127,115,167,1)
    98: (127,115,167,1) #7F73A7FF srgba(127,115,167,1)
    99: (127,116,167,1) #7F74A7FF srgba(127,116,167,1)
    100: (127,117,168,1) #7F75A8FF srgba(127,117,168,1)
    101: (127,117,168,1) #7F75A8FF srgba(127,117,168,1)
    102: (127,118,168,1) #7F76A8FF srgba(127,118,168,1)
    103: (127,118,168,1) #7F76A8FF srgba(127,118,168,1)
    104: (127,119,169,1) #7F77A9FF srgba(127,119,169,1)
    105: (127,119,169,1) #7F77A9FF srgba(127,119,169,1)
    106: (127,120,169,1) #7F78A9FF srgba(127,120,169,1)
    107: (127,121,169,1) #7F79A9FF srgba(127,121,169,1)
    108: (127,121,170,1) #7F79AAFF srgba(127,121,170,1)
    109: (127,122,170,1) #7F7AAAFF srgba(127,122,170,1)
    110: (127,122,170,1) #7F7AAAFF srgba(127,122,170,1)
    111: (127,123,170,1) #7F7BAAFF srgba(127,123,170,1)
    112: (127,124,171,1) #7F7CABFF srgba(127,124,171,1)
    113: (127,124,171,1) #7F7CABFF srgba(127,124,171,1)
    114: (127,125,171,1) #7F7DABFF srgba(127,125,171,1)
    115: (127,125,171,1) #7F7DABFF srgba(127,125,171,1)
    116: (127,126,172,1) #7F7EACFF srgba(127,126,172,1)
    117: (127,126,172,1) #7F7EACFF srgba(127,126,172,1)
    118: (127,127,172,1) #7F7FACFF srgba(127,127,172,1)
    119: (127,128,172,1) #7F80ACFF srgba(127,128,172,1)
    120: (127,128,173,1) #7F80ADFF srgba(127,128,173,1)
    121: (127,129,173,1) #7F81ADFF srgba(127,129,173,1)
    122: (127,129,173,1) #7F81ADFF srgba(127,129,173,1)
    123: (127,130,173,1) #7F82ADFF srgba(127,130,173,1)
    124: (127,130,174,1) #7F82AEFF srgba(127,130,174,1)
    125: (127,131,174,1) #7F83AEFF srgba(127,131,174,1)
    126: (127,132,174,1) #7F84AEFF srgba(127,132,174,1)
    127: (127,132,174,1) #7F84AEFF srgba(127,132,174,1)
    128: (127,133,175,1) #7F85AFFF srgba(127,133,175,1)
    129: (127,133,175,1) #7F85AFFF srgba(127,133,175,1)
    130: (127,134,175,1) #7F86AFFF srgba(127,134,175,1)
    131: (127,135,175,1) #7F87AFFF srgba(127,135,175,1)
    132: (127,135,176,1) #7F87B0FF srgba(127,135,176,1)
    133: (127,136,176,1) #7F88B0FF srgba(127,136,176,1)
    134: (127,136,176,1) #7F88B0FF srgba(127,136,176,1)
    135: (127,137,176,1) #7F89B0FF srgba(127,137,176,1)
    136: (127,137,177,1) #7F89B1FF srgba(127,137,177,1)
    137: (127,138,177,1) #7F8AB1FF srgba(127,138,177,1)
    138: (127,139,177,1) #7F8BB1FF srgba(127,139,177,1)
    139: (127,139,177,1) #7F8BB1FF srgba(127,139,177,1)
    140: (127,140,178,1) #7F8CB2FF srgba(127,140,178,1)
    141: (127,140,178,1) #7F8CB2FF srgba(127,140,178,1)
    142: (127,141,178,1) #7F8DB2FF srgba(127,141,178,1)
    143: (127,141,178,1) #7F8DB2FF srgba(127,141,178,1)
    144: (127,142,179,1) #7F8EB3FF srgba(127,142,179,1)
    145: (127,143,179,1) #7F8FB3FF srgba(127,143,179,1)
    146: (127,143,179,1) #7F8FB3FF srgba(127,143,179,1)
    147: (127,144,179,1) #7F90B3FF srgba(127,144,179,1)
    148: (127,144,180,1) #7F90B4FF srgba(127,144,180,1)
    149: (127,145,180,1) #7F91B4FF srgba(127,145,180,1)
    150: (127,146,180,1) #7F92B4FF srgba(127,146,180,1)
    151: (127,146,180,1) #7F92B4FF srgba(127,146,180,1)
    152: (127,147,181,1) #7F93B5FF srgba(127,147,181,1)
    153: (127,147,181,1) #7F93B5FF srgba(127,147,181,1)
    154: (127,148,181,1) #7F94B5FF srgba(127,148,181,1)
    155: (127,148,181,1) #7F94B5FF srgba(127,148,181,1)
    156: (127,149,182,1) #7F95B6FF srgba(127,149,182,1)
    157: (127,150,182,1) #7F96B6FF srgba(127,150,182,1)
    158: (127,150,182,1) #7F96B6FF srgba(127,150,182,1)
    159: (127,151,182,1) #7F97B6FF srgba(127,151,182,1)
    160: (127,151,183,1) #7F97B7FF srgba(127,151,183,1)
    161: (127,152,183,1) #7F98B7FF srgba(127,152,183,1)
    162: (127,153,183,1) #7F99B7FF srgba(127,153,183,1)
    163: (127,153,183,1) #7F99B7FF srgba(127,153,183,1)
    164: (127,154,184,1) #7F9AB8FF srgba(127,154,184,1)
    165: (127,154,184,1) #7F9AB8FF srgba(127,154,184,1)
    166: (127,155,184,1) #7F9BB8FF srgba(127,155,184,1)
    167: (127,155,184,1) #7F9BB8FF srgba(127,155,184,1)
    168: (127,156,185,1) #7F9CB9FF srgba(127,156,185,1)
    169: (127,157,185,1) #7F9DB9FF srgba(127,157,185,1)
    170: (127,157,185,1) #7F9DB9FF srgba(127,157,185,1)
    171: (127,158,185,1) #7F9EB9FF srgba(127,158,185,1)
    172: (127,158,186,1) #7F9EBAFF srgba(127,158,186,1)
    173: (127,159,186,1) #7F9FBAFF srgba(127,159,186,1)
    174: (127,159,186,1) #7F9FBAFF srgba(127,159,186,1)
    175: (127,160,186,1) #7FA0BAFF srgba(127,160,186,1)
    176: (127,161,187,1) #7FA1BBFF srgba(127,161,187,1)
    177: (127,161,187,1) #7FA1BBFF srgba(127,161,187,1)
    178: (127,162,187,1) #7FA2BBFF srgba(127,162,187,1)
    179: (127,162,187,1) #7FA2BBFF srgba(127,162,187,1)
    180: (127,163,188,1) #7FA3BCFF srgba(127,163,188,1)
    181: (127,164,188,1) #7FA4BCFF srgba(127,164,188,1)
    182: (127,164,188,1) #7FA4BCFF srgba(127,164,188,1)
    183: (127,165,188,1) #7FA5BCFF srgba(127,165,188,1)
    184: (127,165,189,1) #7FA5BDFF srgba(127,165,189,1)
    185: (127,166,189,1) #7FA6BDFF srgba(127,166,189,1)
    186: (127,166,189,1) #7FA6BDFF srgba(127,166,189,1)
    187: (127,167,189,1) #7FA7BDFF srgba(127,167,189,1)
    188: (127,168,190,1) #7FA8BEFF srgba(127,168,190,1)
    189: (127,168,190,1) #7FA8BEFF srgba(127,168,190,1)
    190: (127,169,190,1) #7FA9BEFF srgba(127,169,190,1)
    191: (127,169,190,1) #7FA9BEFF srgba(127,169,190,1)
    192: (127,170,191,1) #7FAABFFF srgba(127,170,191,1)
    193: (127,171,191,1) #7FABBFFF srgba(127,171,191,1)
    194: (127,171,191,1) #7FABBFFF srgba(127,171,191,1)
    195: (127,172,191,1) #7FACBFFF srgba(127,172,191,1)
    196: (127,172,192,1) #7FACC0FF srgba(127,172,192,1)
    197: (127,173,192,1) #7FADC0FF srgba(127,173,192,1)
    198: (127,173,192,1) #7FADC0FF srgba(127,173,192,1)
    199: (127,174,192,1) #7FAEC0FF srgba(127,174,192,1)
    200: (127,175,193,1) #7FAFC1FF srgba(127,175,193,1)
    201: (127,175,193,1) #7FAFC1FF srgba(127,175,193,1)
    202: (127,176,193,1) #7FB0C1FF srgba(127,176,193,1)
    203: (127,176,193,1) #7FB0C1FF srgba(127,176,193,1)
    204: (127,177,194,1) #7FB1C2FF srgba(127,177,194,1)
    205: (127,177,194,1) #7FB1C2FF srgba(127,177,194,1)
    206: (127,178,194,1) #7FB2C2FF srgba(127,178,194,1)
    207: (127,179,194,1) #7FB3C2FF srgba(127,179,194,1)
    208: (127,179,195,1) #7FB3C3FF srgba(127,179,195,1)
    209: (127,180,195,1) #7FB4C3FF srgba(127,180,195,1)
    210: (127,180,195,1) #7FB4C3FF srgba(127,180,195,1)
    211: (127,181,195,1) #7FB5C3FF srgba(127,181,195,1)
    212: (127,182,196,1) #7FB6C4FF srgba(127,182,196,1)
    213: (127,182,196,1) #7FB6C4FF srgba(127,182,196,1)
    214: (127,183,196,1) #7FB7C4FF srgba(127,183,196,1)
    215: (127,183,196,1) #7FB7C4FF srgba(127,183,196,1)
    216: (127,184,197,1) #7FB8C5FF srgba(127,184,197,1)
    217: (127,184,197,1) #7FB8C5FF srgba(127,184,197,1)
    218: (127,185,197,1) #7FB9C5FF srgba(127,185,197,1)
    219: (127,186,197,1) #7FBAC5FF srgba(127,186,197,1)
    220: (127,186,198,1) #7FBAC6FF srgba(127,186,198,1)
    221: (127,187,198,1) #7FBBC6FF srgba(127,187,198,1)
    222: (127,187,198,1) #7FBBC6FF srgba(127,187,198,1)
    223: (127,188,198,1) #7FBCC6FF srgba(127,188,198,1)
    224: (127,189,199,1) #7FBDC7FF srgba(127,189,199,1)
    225: (127,189,199,1) #7FBDC7FF srgba(127,189,199,1)
    226: (127,190,199,1) #7FBEC7FF srgba(127,190,199,1)
    227: (127,190,199,1) #7FBEC7FF srgba(127,190,199,1)
    228: (127,191,200,1) #7FBFC8FF srgba(127,191,200,1)
    229: (127,191,200,1) #7FBFC8FF srgba(127,191,200,1)
    230: (127,192,200,1) #7FC0C8FF srgba(127,192,200,1)
    231: (127,193,200,1) #7FC1C8FF srgba(127,193,200,1)
    232: (127,193,201,1) #7FC1C9FF srgba(127,193,201,1)
    233: (127,194,201,1) #7FC2C9FF srgba(127,194,201,1)
    234: (127,194,201,1) #7FC2C9FF srgba(127,194,201,1)
    235: (127,195,201,1) #7FC3C9FF srgba(127,195,201,1)
    236: (127,195,202,1) #7FC3CAFF srgba(127,195,202,1)
    237: (127,196,202,1) #7FC4CAFF srgba(127,196,202,1)
    238: (127,197,202,1) #7FC5CAFF srgba(127,197,202,1)
    239: (127,197,202,1) #7FC5CAFF srgba(127,197,202,1)
    240: (127,198,203,1) #7FC6CBFF srgba(127,198,203,1)
    241: (127,198,203,1) #7FC6CBFF srgba(127,198,203,1)
    242: (127,199,203,1) #7FC7CBFF srgba(127,199,203,1)
    243: (127,200,203,1) #7FC8CBFF srgba(127,200,203,1)
    244: (127,200,204,1) #7FC8CCFF srgba(127,200,204,1)
    245: (127,201,204,1) #7FC9CCFF srgba(127,201,204,1)
    246: (127,201,204,1) #7FC9CCFF srgba(127,201,204,1)
    247: (127,202,204,1) #7FCACCFF srgba(127,202,204,1)
    248: (127,202,205,1) #7FCACDFF srgba(127,202,205,1)
    249: (127,203,205,1) #7FCBCDFF srgba(127,203,205,1)
    250: (127,204,205,1) #7FCCCDFF srgba(127,204,205,1)
    251: (127,204,205,1) #7FCCCDFF srgba(127,204,205,1)
    252: (127,205,206,1) #7FCDCEFF srgba(127,205,206,1)
    253: (127,205,206,1) #7FCDCEFF srgba(127,205,206,1)
    254: (127,206,206,1) #7FCECEFF srgba(127,206,206,1)
    255: (128,207,207,1) #80CFCFFF srgba(128,207,207,1)
  Rendering intent: Perceptual
  Gamma: 0.454545
  Chromaticity:
    red primary: (0.64,0.33,0.03)
    green primary: (0.3,0.6,0.1)
    blue primary: (0.15,0.06,0.79)
    white point: (0.3127,0.329,0.3583)
  Matte color: grey74
  Background color: srgba(127,59,143,1)
  Border color: srgb(223,223,223)
  Transparent color: black
  Interlace: None
  Intensity: Undefined
  Compose: Over
  Page geometry: 100x112+0+0
  Dispose: Undefined
  Compression: LZW
  Orientation: Undefined
  Properties:
    date:create: 2025-04-08T12:22:36+00:00
    date:modify: 2025-04-08T12:22:36+00:00
    date:timestamp: 2025-04-08T12:22:42+00:00
    mime:type: image/gif
    signature: 6d411e49bbd7a41105d81c29e058277b96e9becfc24f06cabec45d39e928c289
  Artifacts:
    verbose: true
  Tainted: False
  Filesize: 16031B
  Number pixels: 11200
  Pixel cache type: Memory
  Pixels per second: 19.8172MP
  User time: 0.000u
  Elapsed time: 0:01.000
  Version: ImageMagick 7.1.1-44 Q16-HDRI aarch64 22688 https://imagemagick.org

The issue with the 64 colour palette I think we just make a problem for me to fix on ocaml-gif, and for now let's focus on getting this into shape even if it just works for 256 colours.

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

pawaskar-shreya commented Apr 8, 2025

@mdales I am getting the error even now. I have pulled the changes from the upstream. So, the changes are applied. But it does not seem to be working as expected. Theres a stack overflow error. Am I doing something wrong here?

image

image

@mdales
Copy link
Copy Markdown
Collaborator

mdales commented Apr 9, 2025

I am getting the error even now.

I mentioned above that there was still an error I'd not fixed when saving images that aren't 256 colour and I'd not got to that yet, so for now just make your test use a palette of 256 colours, and I'll try work out the 64 bit colour thing as time allows me, but you can still make progress just using a 256 colour test.

Theres a stack overflow error. Am I doing something wrong here?

Yeah - not sure where you picked up the l=10 when you set OCAMLRUNPARAM, but that limits the stack size to 10 frames in total, and we're using recursive code here, where the stack size can be a lot more. You just want to do OCAMLRUNPARAM=b to get the stack trace. Did you try that to limit the output? If so I can see the logic, but alas it doesn't limit how many are displayed, but how many can be used whilst the program runs at all :)

@mdales
Copy link
Copy Markdown
Collaborator

mdales commented Apr 9, 2025

I created claudiusFX/ocaml-gif#6 for the bit depth saving problem

@mdales
Copy link
Copy Markdown
Collaborator

mdales commented Apr 9, 2025

I fixed the bug, try updating now.

I did also find an error in the sample code I gave you: when you flatten the pixel data you need to flatten it to 8 bits per byte, not to the colour depth. See the test code I added here:

https://github.com/claudiusFX/ocaml-gif/blob/main/test/test_gif.ml#L56-L73

At some point Image.t needs a better interface that deals with all the compression for you: claudiusFX/ocaml-gif#8

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

Hey @mdales, first of all really sorry, I really could not communicate and answer to your reviews and suggestions on time. I was trying to manage the contributions along with my college assessment tests. Those are scheduled 7th april to 10th april.

I promised to look into ocaml-gif to make it work for our screenshot feature but most of the error solving was done by you. Again really sorry for that.

I have done all the changes as you told me to do, but since you fixed most of the errors, it's actually working perfectly right now. I will push the changes in a while.

If so I can see the logic, but alas it doesn't limit how many are displayed, but how many can be used whilst the program runs at all :)

Ahhhh, now I see my mistake! That was the reason for the crash.

I had to convert that with imagemagick, as Preview on the Mac didn't like the image, but imagemagick seems to like it fine

Can I replicate this on wsl? Actually I am able to open the screenshot as is and it is opening fine for me.

@mdales
Copy link
Copy Markdown
Collaborator

mdales commented Apr 9, 2025

Hey @mdales, first of all really sorry, I really could not communicate and answer to your reviews and suggestions on time. I was trying to manage the contributions along with my college assessment tests. Those are scheduled 7th april to 10th april.

No need to apologise - your tests are important, good luck to them. I just did the fixes to ocaml-gif because ultimately I'd recommended a library that was broken (by me!) and so wanted to unblock you. If you'd gone with your suggestion of the other graphics library you'd not have had these issues!

I promised to look into ocaml-gif to make it work for our screenshot feature but most of the error solving was done by you. Again really sorry for that.

Again, no apologies needed.

I have done all the changes as you told me to do, but since you fixed most of the errors, it's actually working perfectly right now. I will push the changes in a while.

Excellent. There's still work to do here, but that can wait until after your test tomorrow!

If so I can see the logic, but alas it doesn't limit how many are displayed, but how many can be used whilst the program runs at all :)

Ahhhh, now I see my mistake! That was the reason for the crash.

Yes. I do think it's shown me that I should restructure that code to use the tail-recursion trick in OCaml, which would mean it doesn't need so many stack frames.

I had to convert that with imagemagick, as Preview on the Mac didn't like the image, but imagemagick seems to like it fine

Can I replicate this on wsl? Actually I am able to open the screenshot as is and it is opening fine for me.

I suspect the GIF library is making valid GIFs (imagemagick likes them, and clearly WSL tools too), so this seems to be a Mac problem, so this is on my for now. I'll file another issue over on the other repo for that.

For now, get your tests out the way, and then it'd be great to land this feature, as I'm excited about getting it in.

A question for when you have time: what do you think to scaling the images to match the scaling on screen?

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

I am just about to push the changes and the feature seems to work for a good number of examples, but I remember you mentioning that it might not work for the day2 example and I tested it and it did not work! Is there any way we can make this wrok?

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

A question for when you have time: what do you think to scaling the images to match the scaling on screen?

That would be even better. It would enhance the visuals of the generated art. Right now we are storing the images considering the scaling to be 1. But I can totally see why you recommended this!!!!

screenshot_090425_191640

The day4 example scaled to 2 looks really beautiful 🤩🤩

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

For now, get your tests out the way, and then it'd be great to land this feature, as I'm excited about getting it in.

The feature is almost on the way! Implemented your suggestions and pushing the changes for now, so I can work on any more suggesstions tomorrow after your review once I am done with the test!

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

@mdales I have pushed the changes.

The feature works well with a lot of examples with few exceptions like the day2 example. For some reason github is not showing the review of the screenshot attached of the image that we just got through our feature here.
screenshot_090425_191640

@mdales
Copy link
Copy Markdown
Collaborator

mdales commented Apr 9, 2025

I am just about to push the changes and the feature seems to work for a good number of examples, but I remember you mentioning that it might not work for the day2 example and I tested it and it did not work! Is there any way we can make this wrok?

Oh, that's because day2 has a palette size of 1024 entries, and the biggest GIF palette size is 256. I think down the line we'll want to perhaps also offer PNG for that use case, but that is an outlier in all the examples (and I was cheating - the generative art prompt for day 2 was "no palette" and given you MUST use a palette in Claudius I just went with a really big one so it looked like no palette :D) - for now I think we'll get a lot of use with the GIF output, and as you noted, it leads to save animated gifs one day nicely.

@mdales
Copy link
Copy Markdown
Collaborator

mdales commented Apr 9, 2025

Well done! I'll take a look at the updates tomorrow!

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

that's because day2 has a palette size of 1024 entries

Ahhh, that explains it!

I think down the line we'll want to perhaps also offer PNG for that use case

Yess, I remember your suggestion of adding an abstraction there to be able to have both the gif and png options available to us. That will help solve this!

but that is an outlier in all the examples (and I was cheating - the generative art prompt for day 2 was "no palette" and given you MUST use a palette in Claudius I just went with a really big one so it looked like no palette :D

That's a very hacky way to get to the no palette look. I would love to try this myself also!

for now I think we'll get a lot of use with the GIF output, and as you noted, it leads to save animated gifs one day nicely.

Yesss, that was the very reason I was so determined to make ocaml-gif work for the screenshot feature, it would just make things even better for us in the future eventually.

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

Good afternoon @mdales!

I found the suggestion for saving imgs with scaling very fascinating and so I have tried to add that feature also in the current pr.

I was looking for some scaling algorithms that we can use in our use case and found the Nearest-Neighbor Scaling to be perfect for our use case! For every pixel in the scaled image, we copy the value of the nearest pixel from the original image. I found that it is it preserving the crisp pixel boundaries which seems to be perfect for pixel art and retro graphics that we are doing!

So what we are trying to do is:

  1. We're generating a scaled-up image of size width * scale by height * scale.
  2. For every pixel (x, y) in the scaled image, we:
  3. Map it back to the original image using integer division by scale.
  4. This gives us the coordinates (x0, y0) of the "nearest neighbor".
  5. We then copy that pixel’s value exactly as-is

As it can be seen here that I am using a scale of 7 on the day4 example and the dimensions are scaled as can be seen in the lower left corner

image

image

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

pawaskar-shreya commented Apr 10, 2025

Also @mdales, the builds here seem to be failing as there is no ocaml-gif vendor here which I was using in my local. How do we plan to make this libs accessible for us in the future, so that the builds don't fail? There's also bdfparser lib right that we are planning to use for fonts. We would also have to make it accessible, right?

image

Ohhh also, the tests went really good and I am very happy that now I can focus all my attention here on our contributions and proposal drafting 😊😊

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

I have done all the changes as you told me to do, but since you fixed most of the errors, it's actually working perfectly right now. I will push the changes in a while.

Excellent. There's still work to do here, but that can wait until after your test tomorrow!

What work are we expecting to be done here? I would like to know if I can do it!

Yes. I do think it's shown me that I should restructure that code to use the tail-recursion trick in OCaml, which would mean it doesn't need so many stack frames.

@mdales, do you think I should give this a go?

Oh, that's because day2 has a palette size of 1024 entries, and the biggest GIF palette size is 256. I think down the line we'll want to perhaps also offer PNG for that use case

Also this one. Should I add it in my project proposal or we plan to try to implement it now?

Copy link
Copy Markdown
Collaborator

@mdales mdales left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is looking great. I'll ponder the vendor problem you mentioned, but for now here's some minor comments to address - but otherwise happy to try get this in!

Comment thread src/base.ml Outdated
let palette = Screen.palette s in
let scale = Screen.scale s in
Screenshot.save_screenshot ~scale prev_buffer palette;
false, { input with keys = KeyCodeSet.add key input.keys }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This last line is duplicated, which means if someone has to update it they now. have to do so in two places. Can you restructure this to avoid that?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I will make this change.

Comment thread src/screenshot.mli Outdated
timestamped filename like "screenshot_MMDDYY_HHMMSS.gif" for uniqueness.
The output image is scaled by the given [scale] factor using nearest-neighbor scaling. *)

val color_table_of_palette : Palette.t -> Giflib.ColorTable.t
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't strongly object to this, but my sense is that by having this in the mli file expose that we use ocaml-gif internally, and so if we wanted to move to another image library, then we'd still have to support this API, which isn't ideal?

I could understand it if we were exposing the API to help with tests, but currently we're not.

I guess the thing about APIs is that once you make them, you have to support them, and so whilst there's nothing incorrect about having this, I don't feel it's necessary and so not worth the support overhead.

Does that make sense?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the thing about APIs is that once you make them, you have to support them, and so whilst there's nothing incorrect about having this, I don't feel it's necessary and so not worth the support overhead.

Yeaa, that makes sense. I'll remove it from there!

Comment thread test/test_screenshot.ml Outdated
open Claudius
open Screenshot

let () =
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this use ounit2 like the other test files? That way we get things like progress reports in the tests.

Comment thread src/screenshot.ml Outdated
if i < len then colors.(i) else (0, 0, 0)
)

let save_screenshot ~(scale : int) (fb : Framebuffer.t) (palette : Palette.t) =
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can simplify this interface if you pass in the screen object: that contains the palette and the scale, plus the dimensions of the screen which means you don't need to look at the internal workings of Framebuffer on the next two lines.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it!

Comment thread src/screenshot.ml Outdated
min 8 (max 2 (bits_needed (len - 1) 1))
in

let nulls_replaced = ref 0 in
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this being used for? I can see it being incremented, but then its never used.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it was being used for debugging to check how many 0s were replaced to see if there was any problem in that step. But now it is of no use. I will remove this part

Comment thread src/screenshot.ml Outdated
let src_x = x / scale in
let src_y = y / scale in
let v = fb.data.(src_y).(src_x) in
if v < 0 || v > 255 then
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 255 here should be dependant on the size of the palette?

Comment thread src/screenshot.ml Outdated
failwith (Printf.sprintf "Framebuffer value %d out of byte range at (%d,%d)" v src_x src_y);
if v = 0 then incr nulls_replaced;
let max_index = Palette.size palette - 1 in
let safe_v =
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand what this block is doing. You have the above failwith if there's data out of range, so I'm not sure why this bit is needed? Apologies if I've missed something!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the failwith was to ensure that the framebuffer value is within the valid GIF range (0–255), while the other part was to limit to max_index to ensure that it stays within the bounds of the actual palette, which might be smaller than 256 as you seem to point it out in one of the above comment. But then we can also simplify the logic by either relying on limiting alone or keeping the failwith for validation.

Comment thread src/screenshot.ml Outdated
let y = idx / scaled_width in
let src_x = x / scale in
let src_y = y / scale in
let v = fb.data.(src_y).(src_x) in
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than assume the internals of the Framebuffer, we should either use Framebuffer.read_pixel or you could earlier do Framebuffer.to_array.

Comment thread test/test_screenshot.ml
@mdales
Copy link
Copy Markdown
Collaborator

mdales commented Apr 10, 2025

@mdales, do you think I should give this a go?

Not now - let's get this in with those comments addressed, and that can be a follow on ticket later. What you have done here is already useful!

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

hello @mdales!

I have done all the changes. Could you please tell me if there's any more changes that need to be done?

Copy link
Copy Markdown
Collaborator

@mdales mdales left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking great @pawaskar-shreya - I've asked for one late change, apologies, but hopefully an easy fix and you can see my reasoning, and I spotted how we can vendor the GIF library to get this to pass CI!

Comment thread dune-project
Comment thread src/base.ml Outdated
| `Quit -> (true, input)
| `Key_down ->
let key = PlatformKey.of_backend_keycode (Sdl.Event.(get e keyboard_keycode)) in
if key = Key.F2 then (
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to be a pain, but can you move this to be at the start of the loop, around like 91 perhaps, and just check the input_state?

Two reasons for this:

Firstly. it'll mean we don't clash with @Vanisha1606 who is changing how keyboard events work.

Secondly though, having this here doesn't scale well and obfuscates the event code. This section should just be the mechanics of getting key and mouse events, and then we should somewhere else decide what to do with them. For instance, we have #61 by @Vanshikaxxa also in the wings, which also needs to make a key check to add a debug overlay, and who knows what else we'll later, so I think what you've done here will only get bigger over time as we add more things, so deserves to be in a distinct block of code, not lost in with the event reading.

Does that make sense?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yess, that's true. It'll be hard for us to maintain it later. I'll make this change @mdales .

@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

pawaskar-shreya commented Apr 11, 2025

@mdales just leaving this update here so you can check whenever you get time

When I was testing for the final changes that you suggested and I moved out the screenshot on F2 press section out of Key_down, I noticed something strange happening. There were 2 to 3 simultaneous screenshots being captured. I initially thought I might be calling save_screenshot multiple times, maybe due to not having cleaned up the code while testing. But then later I realized that a long press of F2 was being continuously recorded and hence triggering simultaneous screenshots.

So I decided to handle this by introducing a flag to ensure we only capture a single screenshot per key press. But the code started to grow bigger and clutter the base.ml file. So I thought it would be better to move this into the screenshot module and wrap the core screenshot functionality in a separate function. That’s when I split the responsibilities: the lower-level capture_screenshot function does the actual encoding and writing, while the higher-level save_screenshot handles the key checking and flag control.

But that's when I started facing circular dependency issue. Screenshot.ml needed to access the pressed keys (which lived in base.ml), but base.ml also depended on screenshot.ml to call the screenshot function. So, I read up a bit on OCaml’s compilation model and module dependency resolution, and realized this wasn’t going to work directly as the compiler needs a clear top-down dependency order.

So, to break the cycle, I changed the design so that save_screenshot takes the list of pressed keys (keys : Key.t list) as an argument. That way, base.ml stays in control of input, and screenshot.ml stays focused on handling the save.

But then as save_screenshot was sort of acting like a wrapper around capture_screenshot and we were not able to throw the exception for palette size greater than 256 as this check was done in capture_screenshot and capture_screenshot wasn't triggered in save_screenshot until F2 was pressed. This was causing the buils to fail after we had pinned the ocaml-gif. So, now there's a palette size check in save_screenshot and builds are successful!!

So kudos to you to suggest to move the action on events section out of the events recording section.

If you wouldn't you suggested this change then this bug would have stayed hidden.

My theory of why this thing of multiple screenshots was happening:
When it was placed earlier in the loop, closer to where event input was handled, the frame might not yet have registered the key as "held down" long enough. So we were hitting a narrow window where only one frame observed the key as pressed. Moving the code later meant more time passed, and more frames observed the key down state, triggering the action multiple times. Do tell me if I am thinking in the right direction!

This taught me a very important lesson!
Even small differences in loop timing and event handling placement can have a huge effect when key repeat rates are that fast.

This ticket has been the best ticket I have worked on. Although I have only worked on two tickets 😅😅

What a great learning experience this was!

Have a great weekend!

Copy link
Copy Markdown
Collaborator

@mdales mdales left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done, and a good catch regarding the keypresses. This logic can be simplified once we have #13 added, which lets you see the down and up events on keys, so you could then just check for the F2 key down event - that ticket, being worked on by @Vanisha1606, was motivated by this exact problem :)

And well done on the restructuring - pulling things out of base.ml is in general a good thing I think in terms of making code easier to test and see what is going on, so moving all the logic out there was a win.

@mdales mdales merged commit d5f58bc into claudiusFX:main Apr 12, 2025
1 check passed
@pawaskar-shreya
Copy link
Copy Markdown
Collaborator Author

pawaskar-shreya commented Apr 12, 2025 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add screenshot feature

2 participants