…are the way of the future.
Everyone’s familiar with the almost-ubiquitous QR codes. They appear no matter where you go - cereal advertisements, fliers for prom, even terrorist communications:
As a side note, I would like to thank Kaywa for their free QR code generator. Without their continuing support I would have to use something else, or else (heaven forbid) generate my own QR codes.
Now, QR codes are cool and all, but they’re a little bit commonplace now. They used to be the hip thing. Now everyone who has a smartphone has a QR code reader. I was thinking the other day about my work with SSTV (Slow Scan TeleVision) - essentially, images transferred over sound waves.
Then I saw SpectroTyper, which is very cool oh-by-the-way. It essentially builds an audio file where the spectrograph displays some human readable text.
So I said to myself: I can do that.
And I wrote a little script that will convert an arbitrary QR code into an audio file. You give it data, it spits out a wave file. Here’s an example audio file:
I’ll be the first to admit that it doesn’t sound like much. But before you scoff at me too much, pull it into Audacity and look at the spectrograph:
Does that look like something we all know? Scan it if you don’t believe me.
The backend script to generate that isn’t terribly complex. Essentially, it reads an image, and wherever the image is black it adds a cosine wave of the appropriate frequency to that time bucket. Then it normalizes everything and we’re finished.
Feel free to play around with the QR code generator - however, bear in mind that it’s generating each QR code audio bit from scratch on a Pentium 4. So when you submit a job I’ve found it’s usually taken about a minute or two, but jobs are processed linearly so don’t abuse it.
Also, I believe I promised to release the source code for the HDR. Here’s the code:
import Image, ImageOps, ImageChops, math, argparse, os, ImageFilter, ImageDraw # Computes F(t): # - a is the lower asymptote, k is the upper one. # - b is the max. growth rate. # - v is a symetry constant. Keep at 0.5. def logistic(t, a, k, b, v, m): swapped = False if k<a: tmp = k k = a a = tmp swapped = True retval=(k-a) / math.pow(1.0 + v * math.pow(2.7182818, -b*(t-m)), 1.0/v) if swapped: retval = (-k+a)+retval retval += a return retval def parabola(t, coef, cx, cy): return cy + coef*(t-cx)*(t-cx) def drawHistogram(histogram, log=False): maximum = 0 for h in histogram: if h > maximum: maximum = h img = Image.new("RGBA", (len(histogram), len(histogram))) draw = ImageDraw.Draw(img) cnt = 0 for h in histogram: if h == 0: y = 0 else: if log: y = math.log(float(h)) / math.log(maximum) else: y = float(h) / maximum #print h, maximum, y draw.line([(cnt, len(histogram)), (cnt, (1.0-y)*len(histogram))]) cnt += 1 pass return img # Assumes the domain of rn is the 0<x<width of image def overlayFunction(img, fn): draw = ImageDraw.Draw(img) last_v = (0, 256-fn(0)) for x in range(img.size[0]): new_v = (x, 256-fn(x)) draw.line([last_v, new_v], (255,0,0)) last_v = new_v pass return img # Light_coef is the amount that the brighter image is used, # dark_coef is the same but for the darker image. def generateHDR(medium, dark, light, dark_coef=2.0, light_coef=2.0): # Load the light, dark, and medium images. im_dark = Image.open(dark).convert() im_med = Image.open(medium).convert() im_light = Image.open(light).convert() debug = Image.new("RGB", (im_dark.size[0]*4, im_dark.size[1]*6)) draw = ImageDraw.Draw(debug) w = im_dark.size[0] h = im_dark.size[1] debug.paste(im_med, (0,0)) debug.paste(im_dark, (w,0)) debug.paste(im_light, (w*2,0)) debug.paste(im_med, (0,h)) debug.paste(im_dark, (w,h)) debug.paste(im_light, (w*2,h)) # Create the light and dark layer masks im_dark_mask = ImageOps.grayscale(im_dark) im_light_mask = ImageOps.grayscale(im_light) im_med_mask = ImageOps.grayscale(im_med) debug.paste(im_med_mask, (0,h*2)) debug.paste(im_dark_mask, (w,h*2)) debug.paste(im_light_mask, (w*2,h*2)) steepness = 0.03 minimum = 150.0 maximum = 256.0 # Remember: 256 is 100% transparent, 0 is 100% opaque def darkCurve(i): retval = int(256-logistic(float(i)-100.0, minimum-120, maximum-120, -steepness, 0. 5, 0.0)) return retval-120 def lightCurve(i): retval = int(logistic(float(i)-190.0, minimum, maximum, steepness, 0.5, 0.0)) return 256-retval def mediumCurve(i): retval = int(256-parabola(float(i), 0.01/127.0, 127.0, 256.0)) return retval debug.paste(overlayFunction(drawHistogram(im_med_mask.histogram()), mediumCurve).resiz e((w,h)), (0, h*3)) debug.paste(overlayFunction(drawHistogram(im_dark_mask.histogram()), darkCurve).resize ((w,h)), (w*1, h*3)) debug.paste(overlayFunction(drawHistogram(im_light_mask.histogram()), lightCurve).resi ze((w,h)), (w*2, h*3)) im_dark_mask = im_dark_mask.point(darkCurve) im_light_mask = im_light_mask.point(lightCurve) im_med_mask = im_med_mask.point(mediumCurve) debug.paste(im_med_mask, (0,h*4)) debug.paste(im_dark_mask, (w,h*4)) debug.paste(im_light_mask, (w*2,h*4)) # Create a new result image result = im_med debug.paste(result, (0, h*5)) # Add the light image through the mask result = Image.composite(im_dark, result, im_dark_mask.filter(ImageFilter.BLUR)) debug.paste(result, (w, h*5)) result = Image.composite(im_light, result, im_light_mask.filter(ImageFilter.BLUR)) debug.paste(result, (w*2, h*5)) debug.paste(result, (w*3, h*5)) debug.paste(drawHistogram(ImageOps.grayscale(result).histogram()).resize((w,h)), (w*3, h*4)) debug.save("debug.jpg") return result parser = argparse.ArgumentParser() parser.add_argument("--file-list", nargs='*', help="Processes the list of files specified. Files are interpreted in sets of three, where the first is the medium, the next is the da rkest, and the final in the set is the brightest.") parser.add_argument("--output-dir") args = parser.parse_args() if args.file_list: if len(args.file_list) > 0 and len(args.file_list)%3 == 0: output_dir = args.output_dir if not output_dir: output_dir = "result" try: os.mkdir(output_dir) except OSError: pass for i in range(0, len(args.file_list), 3): result = generateHDR(args.file_list[i], args.file_list[i+1], args.file_list[i+ 2]) result.save("%s/%s"%(output_dir, args.file_list[i].split("/")[-1])) else: print "You must have more than zero, and a multiple of three files." else: print "Send us some arguments! (-h for more)"