# Feature Request: Raw RGB values in preview JPG

**URL:** <https://forum.electromage.com/t/feature-request-raw-rgb-values-in-preview-jpg/1791>\
**Category:** Feature Ideas + Requests\
**Created:** [December 2, 2021, 10:54pm UTC](https://forum.electromage.com/t/feature-request-raw-rgb-values-in-preview-jpg/1791 "2021-12-02T22:54:23Z")\
**Posts on this page:** 1\
**Showing post:** 5

<div class="post-metadata">

**Author:** ![pixie](https://forum.electromage.com/letter_avatar_proxy/v4/letter/p/7bcc69/32.png) [@pixie](https://forum.electromage.com/u/pixie)\
**Post date:** [December 3, 2021, 7:20pm UTC](https://forum.electromage.com/t/feature-request-raw-rgb-values-in-preview-jpg/1791/5 "2021-12-03T19:20:59Z")

</div>

@wizard, are you saying we could have **more** pixels in the preview? If you’re willing to accept the knock-on impact on the pattern library or editor UI code, it might be nice if the preview matched the `pixelCount` of the device. Plenty of off-the-shelf strings (144/m), matrices (8x32, 16x16), and rings (241, 256, fibonacci) have more than 100 pixels.

@scruffynerf, I’m not using the pixel map at the moment (just transposing each pixel into a rectangular `height` x `width` grid) but if @wizard wants to tell me how to get the final map coordinates from the `pixelmap.dat` blob I could try to put the pixels in the right place for more complex layouts.

~-~-~-~-~

Anyway, if the previews will be fixed eventually then I might as well release it now. The forum won’t let me post .PY or .ZIP files, so here it is as a text blob:

> **animatePreview.py**
>
> ```auto
> import argparse, os, io, json, base64, struct
> from PIL import Image
> 
> # ------------------------------------------------
> 
> # Here's where the magic happens.
> def animatePreview(patternPreview, patternWidth, patternHeight, patternFrames, outputScale, borderColor, filenameBase, verbose):
>         
> # Unpack the bencode'd string into an Image so we can process it as RGB pixels.
> with Image.open(io.BytesIO(base64.b64decode(patternPreview))) as imgPreview:
> 
> # Calculate how big things need to be.
> if patternWidth == 0:
> patternWidth = imgPreview.width
> if patternFrames == 0:
> patternFrames = imgPreview.height
> outputWidth = patternWidth * (1 + outputScale) + 1
> outputHeight = patternHeight * (1 + outputScale) + 1
> 
> # Debugging output
> if verbose:
> print(" imageWidth: ", imgPreview.width)
> print(" imageHeight: ", imgPreview.height)
> print(" patternWidth: ", patternWidth)
> print(" patternHeight: ", patternHeight)
> print(" patternFrames: ", patternFrames)
> print(" outputScale: ", outputScale)
> print(" outputWidth: ", outputWidth)
> print(" outputHeight: ", outputHeight)
> 
> # Start pantographing pixels from the JPG into the animated PNG.
> animationFrames = []
> for iterRow in range(patternFrames):
> # Create a new blank frame.
> animationFrame = Image.new('RGB', (outputWidth, outputHeight), (borderColor, borderColor, borderColor))
> 
> maxBrightness = 0
> for iterCol in range(patternWidth * patternHeight):
> #for iterCol in range(min(patternWidth * patternHeight, imgPreview.width)):
> r, g, b = imgPreview.getpixel((iterCol, iterRow))
> maxBrightness = max(maxBrightness, r, g, b)
> pixelX = 1 + (iterCol % patternWidth) * (1 + outputScale)
> pixelY = 1 + (iterCol // patternWidth) * (1 + outputScale)
> for hPixel in range(outputScale):
> for vPixel in range(outputScale):
> animationFrame.putpixel((pixelX + hPixel, pixelY + vPixel), (r, g, b))
> 
> # save the frame.
> animationFrames.append(animationFrame)
> 
> # save the output file.
> outputFilename = filenameBase + '.png' 
> if True or verbose:
> print("Saving", len(animationFrames), "frames to", outputFilename)
> animationFrames[0].save(outputFilename, save_all=True, append_images=animationFrames[1:], duration=40, loop=0)
> 
> # Test for excessive dimming of preview image.
> if maxBrightness < 128:
> print("Warning: the brightest pixel in the preview image is less than 50%")
> print(" ...to get an accurate representation of the pattern, it is necessary ")
> print(" ...to set both the Brightness slider and maxBrightness (under Settings)")
> print(" ...to 100%, and then save and re-export the pattern.")
> 
> # ------------------------------------------------
> 
> # main
> if __name__ == " __main__":
> 
> # Parse command line.
> parser = argparse.ArgumentParser()
> parser.add_argument("patternFile", help="The pattern (EPE) to be animated")
> parser.add_argument("--width", type=int, default=0, help="The width of the pattern (if less than the preview size of 100)")
> parser.add_argument("--height", type=int, default=1, help="The height of the pattern if 2D; defaults to 1")
> parser.add_argument("--frames", type=int, default=0, help="The number of frames to render (if less than the preview size of 150)")
> parser.add_argument("--scale", type=int, default=5, help="How much to magnify each pixel of the preview; defaults to 5")
> parser.add_argument("--border", type=int, default=63, help="8-bit grayscale value for the border lines; defaults to 63")
> parser.add_argument("--verbose", action='store_true', help="Display debugging output")
> args = parser.parse_args()
> 
> # Read the pattern archive (.EPE) file and pass the preview image to our converter.
> with open(args.patternFile, 'rb') as inputFile:
> 
> # Debugging output
> if args.verbose:
> print("Extracting preview image from", args.patternFile)
> EPE = json.load(inputFile)
> 
> # Check whether this is ought to be a 2D render, but isn't.
> if "render2D(" in EPE['sources']['main']:
> if args.height == 1:
> print("Warning: this pattern contains a 2D renderer; if you want a 2D preview you need to specify the height.")
> 
> # Create the preview animation.
> inFilename, inExtension = os.path.splitext(args.patternFile)
> animatePreview(EPE['preview'], args.width, args.height, args.frames, args.scale, args.border, inFilename, args.verbose)
> 
> # close the input file.
> inputFile.close()
> 
> ```

I wrote it in Python because I also call it from inside the Python-based backup script I built using the Pixelblaze client libraries developed by @zranger1 and @Nick_W. Anyone on a Mac should already have all the dependencies to run this; anyone on a PC can use Python for Windows or run it in their favorite Linux distribution using WSL.

Syntax is as follows:

```auto
usage: animatePreview.py [-h] [--width WIDTH] [--height HEIGHT] [--frames FRAMES] [--scale SCALE]
                         [--border BORDER] [--verbose]
                         patternFile

positional arguments:
  patternFile The pattern (EPE) to be animated

optional arguments:
  -h, --help show this help message and exit
  --width WIDTH The width of the pattern (if less than the preview size of 100)
  --height HEIGHT The height of the pattern if 2D; defaults to 1
  --frames FRAMES The number of frames to render (if less than the preview size of 150)
  --scale SCALE How much to magnify each pixel of the preview; defaults to 5
  --border BORDER 8-bit grayscale value for the border lines; defaults to 63
  --verbose Display debugging output

```

Some examples:

```auto
$ python3 animatePreview.py Cylon.epe
Saving 150 frames to Cylon.png

```

![Cylon](https://forum.electromage.com/uploads/default/original/2X/9/90925c2ac9fac634b73fd19d0c55ce04cd19929c.png)

There’s not much going on with the rightmost 36 pixels because the pattern file was exported from an 8x8 matrix. The animation also jumps at the end because the default preview length of 150 frames isn’t an integer multiple of the pattern length. That’s easily fixed:

```auto
$ python3 animatePreview.py Cylon.epe --width=64 --frames=136
Saving 136 frames to Cylon.png

```

![Cylon](https://forum.electromage.com/uploads/default/original/2X/1/1d53654110472aab20a19f9f9b29f8c32c534f35.png)

Or how about a 2D pattern?

```auto
$ python3 animatePreview.py --border=32 Breakout.epe
Warning: this pattern contains a 2D renderer; if you want a 2D preview you need to specify the height.
Saving 150 frames to Breakout.png

```

![Breakout](https://forum.electromage.com/uploads/default/original/2X/8/8743ff63ee6ea66cdcd49437cdca8fa9295aefc4.png)

Oops! Trying again with the `--width` and `--height` options to use the 2D renderer:

$ python3 animatePreview.py --border=32 --width=8 --height=8 Breakout.epe  
Saving 150 frames to Breakout.png  
 ![Breakout](https://forum.electromage.com/uploads/default/original/2X/8/8904a2032709562c352b58ad83bbbcd119058efb.png)

And if you like bigger pixels and black borders:

```auto
$ python3 animatePreview.py --scale=10 --border=0 --width=8 --height=8 cube\ fire\ 3D.epe
Saving 150 frames to cube fire 3D.png

```

![cube fire 3D](https://forum.electromage.com/uploads/default/original/2X/1/118275b4725ade564d5798b28e9cd0ff5313e25d.png)

So there it is. If you give it a try, leave any feedback here.

---

_[View the full topic](https://forum.electromage.com/t/feature-request-raw-rgb-values-in-preview-jpg/1791)._
