-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[feature] data uri image handling, new ranges, fix docs
- Loading branch information
Showing
3 changed files
with
204 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |