From 0ea75031c24eaa9c0c95fda7f38feb81d2028798 Mon Sep 17 00:00:00 2001 From: Lawrence Aberba Date: Thu, 30 Jul 2020 17:46:47 +0000 Subject: [PATCH] [feature] data uri image handling, new ranges, fix docs --- source/nyinaa/image.d | 115 ++++++++++++++++++++++++++++++++++++++++ source/nyinaa/package.d | 4 ++ source/nyinaa/range.d | 85 +++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 source/nyinaa/image.d create mode 100644 source/nyinaa/range.d diff --git a/source/nyinaa/image.d b/source/nyinaa/image.d new file mode 100644 index 0000000..5a7bf8a --- /dev/null +++ b/source/nyinaa/image.d @@ -0,0 +1,115 @@ +module nyinaa.image; + +/*********************************** +Checks if a string is a valid image data URI string. Currently suports JPG, PNG, GIF, WebP, SVG and BMP. +See https://en.wikipedia.org/wiki/Data_URI_scheme + +Params: + dataURI = the base 64 string to do check on + */ + +auto isImageDataURI(string dataURI) +{ + import std.algorithm : startsWith, canFind; + import std.string : strip; + import std.array : split; + + if (!dataURI.length) + return false; + + immutable allowedFormats = [ + "image/png", "image/jpg", "image/jpeg", "image/webp", "image/gif", + "image/svg", "image/bmp" + ]; + + string prefix = dataURI.split(";")[0]; + + // base64 image must start with a data: prefix + if (!prefix.startsWith("data:")) + return false; + + string type = prefix.strip("data:").strip; + return allowedFormats.canFind(type); +} + +/// +unittest +{ + assert(isImageDataURI("data:image/gif;base64,R0l")); + assert(!isImageDataURI("datax:image/gif;base64,R0l")); +} + +/** +Converts an image data URI string to an image buffer. Currently suports JPG, PNG, GIF, WebP, SVG and BMP. See https://en.wikipedia.org/wiki/Data_URI_scheme + +Params: + dataURI = the data URI string to convert to an image buffer. + */ +auto bufferFromDataURI(string dataURI) +{ + import std.array : split; + import std.string : strip; + import std.base64 : Base64; + import std.stdio : writeln; + import std.outbuffer : OutBuffer; + import std.algorithm : startsWith, canFind, countUntil; + + // Image data format + struct ImageData + { + // image buffer + OutBuffer buffer; + + // image mime type + string extension; + + // image mime type + string mimeType; + } + + if (!isImageDataURI(dataURI)) + throw new Exception("Invalid image data URI"); + + immutable metaData = dataURI.split(',')[0]; + string prefix = metaData.split(';')[0]; + + immutable typeExtensions = [ + "image/png" : ".png", "image/jpg" : ".jpg", "image/jpeg" : "j.peg", + "image/webp" : ".webp", "image/gif" : ".gif", "image/svg" : ".svg", + "image/bmp" : ".bmp" + ]; + + string mimeType = prefix.strip("data:").strip; + + /// strip out meta data prefix and + // strip out comma after "base64" + debug writeln("metaData: ", metaData); + debug writeln("prefix: ", prefix); + string base64Data = dataURI.strip(metaData).strip(","); + + string extension = typeExtensions[mimeType]; + debug writeln(extension, " ", mimeType); + + ubyte[] data = Base64.decode(base64Data); + OutBuffer buffer = new OutBuffer(); + buffer.write(data); + + return ImageData(buffer, extension, mimeType); +} + +/// +unittest +{ + import std : readText, writeln; + + const text = readText("./snippets/data-uri.txt"); + + const result = bufferFromDataURI(text); + + // buffer is of type `OutBuffer` + // See `std.outbuffer.OutBuffer` + writeln(result.buffer.toBytes); + + writeln(result.mimeType); + writeln(result.extension); +} diff --git a/source/nyinaa/package.d b/source/nyinaa/package.d index 97724e3..c549395 100644 --- a/source/nyinaa/package.d +++ b/source/nyinaa/package.d @@ -1,11 +1,15 @@ /++ Nyinaa (meaning "all" or "everything" in Twi language) is an all-in-one collection of reusable functions & utilities (timers, validators, sanitizers, ranges, logging, ...) +Some of these utility function were gathered from random places including the D forum. Authors are unknown. + Its goal is to provide convenience function commonly used when developing applications such a web development, desktop application and the like. Many functions are not implemented yet. If you want something that is not already available, please submit a pull request OR file an issue at https://github.com/aberba/nyinaa/issues +/ module nyinaa; +public import nyinaa.image; +public import nyinaa.range; public import nyinaa.timers; public import nyinaa.sanitizers; public import nyinaa.validators; diff --git a/source/nyinaa/range.d b/source/nyinaa/range.d new file mode 100644 index 0000000..938ade4 --- /dev/null +++ b/source/nyinaa/range.d @@ -0,0 +1,85 @@ +/** + Handy functions for use in range related tasks +*/ + +module nyinaa.range; + +/*********************************** + * creates a concrete range (std.container.array.Array range) out of an eager range that can for example be used with `std.range.sort()` without requiring ``.array()` in the chain. This helps avoid allocating extra garbage on the heap as in the case of `.array()`. So it's perfect for temporal arrays in chained pipelining. + + Credit to Steven Schveighoffer for the tip at https://forum.dlang.org/post/rfk5j3$lpo$1@digitalmars.com + +Params: + r = the eager range to be processed + +Example: +--- + + /++ + Suppose I have the an array which is mapped with a function `doSomething()` + as a predicate that does a lot of processing on each element + you want to use `sort()` after the mapping, and there are more operations + done to the range after sort: + +/ + auto arr = [20, 30, 40,50]; + arr.map!doSomething.sort(...)...; + + // `sort()` fails because the range it receives doesn't support element swapping. + // This might be resolved by calling `array()`: + arr.map!doSomething.array.sort. ...; + + /++ + However, `array()` triggers an allocation which might not be + the desired behavior you want. + `concreteRange` can be used to resolved the issue. + Its creates a temporary array without allocating extra + garbage on the heap. + +/ + arr.map!doSomething.concreteRange.sort(...)...; +--- + */ +auto concreteRange(Range)(Range r) +{ + import std.range : ElementType; + import std.container.array : Array; + + // Slicing the Array with `[]` will keep the reference count correctly, and + // destroy the memory automatically after you're done using it. + return Array!(ElementType!Range)(r)[]; +} + +/// use for structs with a `present` and `value` member. +// alias orElse = (a, b) => a.present ? a.value : b; + +/** + used for two values of same types (integer, strings, struct, etc) and a + custom comparison function, `cmpFunc`. `cmpFunc` is used to determine + whether first argument passes else returns second. + +Params: + value = the first value used in comparison function + fallback = fallback value to returned when first fails the comparison +*/ + +T orElse(T, alias cmpFunc)(T value, T fallback) +{ + return cmpFunc(value) ? value : fallback; +} + +// +unittest +{ + alias compareFunc = (a) => a == 1; + assert(0.orElse!(int, compareFunc)(1) == 1); +} + +/** alias for .then which is useful for range concatenation + * Example: +--- +auto triples=recurrence!"a[n-1]+1"(1.BigInt) + .then!(z=>iota(1,z+1).then!(x=>iota(x,z+1).map!(y=>(x,y,z)))) + .filter!((x,y,z)=>x^^2+y^^2==z^^2); +triples.each!((x,y,z){ writeln(x," ",y," ",z); }); +--- + */ +alias then(alias a) = (r) => map!a(r).joiner;