• Adjustable Effects, the math

    Todd12/06/2017 at 05:05 0 comments

    Here's where things went really poorly... I thought I could adjust the values of the matrix such that it would increase or decrease the intensity of the effects. And maybe I could, but the frame rate was already low, so I stuck to integer math and tried to avoid division by using shifts, but the equivalent division by 2, 4, 8 was really limiting I think. And I had already substituted in a division in for the square root that supposed to be part of the Kernel/convolution matrix calculation.

    The result was the effects quality suffered pretty greatly and the "adjustable" feature just made it worse (or was unnoticeable).

    With the exception of Unstabilise which responds well - from essentially no effect to shakier than the original. So at least something worked. ;)

  • Adjustable Effects, hardware

    Todd12/06/2017 at 04:49 0 comments

    So once I identified the analog pins, it worked out perfectly because they're exposed on the front (camera side) of the badge. I soldered in the Spectra ThinPot 50mm 10K. While the three I needed are right in a row, they're not in the right order so I had to cross one over (thus the electrical tape insulation). 

  • Adjustable Effects, reading Analog

    Todd12/05/2017 at 22:09 0 comments

    Especially when I had this idea that I would be able to enhance the UI such that I could create an expanded menu of options and thus more control over any individual effect, I started thinking what would be the best UX for that level of range of control. Then I remembered I had a number of Spectra ThinPots, and they work well with MCU's.

    At first I wasn't sure if there was an analog input; pouring over the datasheet and schematic I wasn't sure.... eventually I realized the pin naming was confusing, and there were a couple analog inputs available on a few pins. I guess if I had paid attention to the Scope and looked at the scope.c code it would have been obvious :/

    ProTip: always read the source code.

    I guess deceived by Arduino, I thought reading from an analog input would be easy, but ... Once I "discovered" the scope.c code I just copied from it, but since it uses interrupts (which I didn't need) I started cutting out code.... and I cut too deep. That was a waste of a couple hours. I had to cull through this:

    Read more »

  • Saving Images Post-Effect

    Todd12/05/2017 at 09:05 0 comments

    The most obvious deficiency of the original code is while it had some good effects, they were transient only - you couldn't save them.

    I thought implementing this would be tough, but looking at camera.c, and copying code from just a couple functions, and a few variable definitions, it was easier than expected.

    The `docamname()`, and `s_camgrab`, `s_camwait` and `s_camquit` case blocks, were all that were needed.

  • UI Enhancements

    Todd12/05/2017 at 08:57 0 comments

    The original code just had a button labeled "Effect" which simply increments; the code handled this in a clever, simple way: if the `default` condition of the `switch (effect) {}` block was run, the effect counter would be reset to 0.

    In this way it's easy to add more effects; no "max" or "stop" conditional needs to maintained nor coded. But, while cycling through the 4 original effects is fine, doing so through the 8 or 10 would be annoying. 

    So I changed it to "Efct+", and then to add "Efct-" I decremented the index, and only added a check to make sure it didn't go below 0. The only thing this didn't handle is you couldn't decrement below 0 and then wrap around to the last effect; I felt this was a (well, lazy) good compromise.

    I thought about making other UI improvements, like "rotating" menus allow control of other things besides Exposure Lock and Light, but didn't get to it.

  • Kernel/convolution matrix processing

    Todd11/30/2017 at 04:05 0 comments

    Wikipedia can explain Kenel's for image processing better than I can:

    Convolution is the process of adding each element of the image to its local neighbors, weighted by the kernel. This is related to a form of mathematical convolution.

    Image Kernels: Explained Visually, has an interactive demo

    The existing code seems to do this in the dither function but it's not really reusable:

    for(y=0 ; y != ypixels; y++) {
      for (x = 0; x != xpixels; x++) {
        inptr = cambuffer + bufstart + x + y * xpixels;
        op = *inptr;                   
        if (op > 0x80) np = 0xff; else np = 0;
        er = op - np;
        *inptr = np;
        inptr++; //right
        z = (int) *inptr + er * 7/16;
        if (z<0) z = 0; else if (z > 255) z = 255;
        *inptr = z;
        /* snip */
      } // x
    } // y

    Here's an example of the Boxcar (smoothing) Kernel. Unlike the example in Wikipedia, these are not 1's - but I was trying to get some variation, as we'll see later.

    const Kernel_t BoxcarKernel = {
      {7, 7, 7},
      {7, 7, 7},
      {7, 7, 7},

    One thing I learned the hard way was you can't modify the matrix in-place; in other words the result of the calculation has to go into a new array. But where to put it? The answer was in the dither function but I failed to realize that at the time. I tied allocating the entire frame buffer again, but there's not sufficient RAM for that. The trick is that the frame buffer is allocated for RGB with 1 byte per color per pixel, while the effects only work on Greyscale - 1 byte total per pixel. 

    I don't know about you but since I'm only an occasional C coder, so things like pointers, addresses and the like really hurt my brain.

    So I planned to pass a matrix (only a 3x3 array - in the interest of performance), along with the from and to buffers, and a some other values to help in the calculations.  Here's the function signature:

    void convolution(mono_buffer_t sourceBuffer, mono_buffer_t targetBuffer, Kernel_t Kernel, signed int scaler, signed int offset)

    Then it's just a matter of doing some prep, and calling the convolution code:

    convolution(cambuffer + bufstart, cambuffer + bufstart + cambuffmono_offset, Kernel, 5, 0);

    And then displaying it - just be tweaking the existing display code to work with my "new" buffer definitions:

    monopalette (0,255);
    dispimage(0, 12, xpixels, ypixels, (img_mono | img_revscan), cambuffer + bufstart + cambuffmono_offset);

  • Summary of results

    Todd11/28/2017 at 22:09 0 comments

    So what did I accomplish:

    1. The badge came with Slowscan, Ghost, Unstabilize and Dither. I decided to make a generic kernel/ convolution matrix processing function I could use in a variety of ways. more about that later.
    2. The UI came with an "Effect" button that looped through the effects. I changed it so there's a forward and back button. Genius, right?
    3. I thought saving the processed image was going to be the hardest part, but like so many of these hacking situations - it's all in reusing what you can.
    4. Adding HW to adjust the effects seemed easy enough at first.... but the corollary to #3 is it's easy (or perhaps just to me) to misunderstand and misuse existing code.

    Details on each to follow.