I’m preparing a poster for a conference next week, and you got me sold on this #betterposter thing @cormullion. I’ll be linking to one of my Julia packages on github, but I’d love to shove Julia in there if I can. But unlike you I’d rather play it a bit more safe and have the julia colors a bit washed out. So I added
I love UnicodePlots! I don’t think any QR code could read them though, so that’s probably not gonna work out. ImageInTerminal looks better, but it seems it’s very terminal-dependent!
This is exactly what I hoped would happen when I decided to expose the BitArray instead of an image, I wanted people to use it in creative ways. Thanks guys! It was totally worth it ^^
If you wanted to, you could return an Array{Gray{Bool},2} (from Colors.jl) which would behave as an array with respect to functions like getindex but like an image for operations like show and save. Best of both worlds, somehow.
Interesting! However I might lose the ability to draw QR code in different colors, or do something more unexpected maybe. It’s very simple to transform to Array of Gray (Gray.(.! qrcode("hello"))), but the opposite is more complicated…
That actually is a very good point. Is there a way to define some sort of show function that would output an image without changing the structure? I guess I’d have to make it a struct then…
That’s the idea - wrap it in a struct and override Base.show. Though there’s an attractive simplicity in just returning a BitArray
If you do want to go with a wrapper type you might use QRCode though you’d have to rename your package to QRCodes.jl (module name pluralization is an ecosystem convention to deal with this, eg Colors.Color, Dates.Date, Rotations.Rotation etc etc)
If you made a wrapper, what interface would you want it to conform to other than having a nice default show method? Eg, is it a graphical object, an AbstractMatrix or something else entirely? Perhaps best to keep it simple:
struct QRCode
data::BitMatrix
# Add more things here, eg the code version,
# original source data string, etc
end
function Base.show(io::IO, code::QRCode)
# You can include the original text in a header line too if it's part of QRCode
# Minimal ANSI color text representation:
for i=1:size(code.data,1)
println(io, join([code.data[i,j] ? "\e[30m██" : "\e[97m██" for j=1:size(code.data,2)]))
end
end
# possibly also `show(io::IO, ::MIME"text/html", qrc::QRCode)` for IJulia and the like?
Looking at your API I think it would be useful to make the export list more minimal. Having a small export list means you introduce few concepts (types - nouns; functions - verbs) for users to learn about. A minimal set of exported names is also less likely to clash with other packages. Here’s some things to try:
Avoid exporting really generic names like getversion if possible. The question to ask is: do I have a similarly generic meaning to go with each generically named function I export? If not, either make the names more specific, resist exporting them, or find a generic function from a different package which has the correct semantics and extend that.
It’s possible to model ErrCorrLevel with types like you’ve done but modelling it with values would be simpler. I’d be inclined to just pass symbol values :low, :medium, :high for the error correction levels, then you can remove those extra types. Similarly for Mode and its subtypes.
With a type QRCode to model your data, you may get away with exporting only QRCode as the public API:
qrcode would become the constructor QRCode
exportqrcode might become save(path, code::QRCode)
getversion and getmode might be handled several ways depending on your intent:
renamed as exported functions qrversion and qrmode
accessible as public fields of QRCode
documented as public non-exported functions QRCodes.version and QRCodes.mode
https://github.com/rafaqz/UnicodeGraphics.jl can also convert arrays to braile and block chars. Pretty much the same thing but fast (for showing live simulations over ssh hahah)
Thank you very much for all the advice. It all looks like fun things to do with Julia, especially as a learning exercise. I think I’m leaning towards leaving it as simple as possible, add a few options for exporting graphics (png, eps, pdf, svg…) and let people go nuts if they want something more. But I’ll think about it more.
I’m surprised you tell me to export less things, I thought I was being minimalistic already ^^
But thank you, I’ll consider your advice.
As for ErrCorrLevel and Mode, I love type programming too much to give them up (I’m a Haskell kind of guy). I really enjoyed combining multiple dispatch and types to define the different encoding functions.
The problem with the block chars approach (used by UnicodeGraphics and ImageInTerminal) is that the line spacing can introduce thin white lines through the image. Not much of a problem for plots, but it makes QR-codes unscannable. For example, on my (MacOS default) terminal it looks like this:
Yeah it will in atom. It doesnt in a lot of linux terminal fonts which is the real use case. Its probably not usefull for this problem, I was just pointing out there are easier ways than unicodeplots to print arrays and in the repl as people were suggesting that
Great! Programming with multiple dispatch can be very satisfying. There are some circumstances where dispatching on types like this does make a lot sense (from an API standpoint). For example, if the user needs to be able to define their own type and expect the internals of the package to dispatch back to a user-defined function. I don’t know much about QR codes, but I guess they’re defined by a standard and not really extensible in this way. Another case would be when you’ve got tight code in an inner loop and you can’t afford the cost of dispatching on the value. Using types in that case could get a speedup because the compiler will generate type-specialized code for each “selector type”.