Skip to content

Commit

Permalink
[feature] data uri image handling, new ranges, fix docs
Browse files Browse the repository at this point in the history
  • Loading branch information
aberba committed Jul 30, 2020
1 parent aada7a7 commit 0ea7503
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 0 deletions.
115 changes: 115 additions & 0 deletions source/nyinaa/image.d
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);
}
4 changes: 4 additions & 0 deletions source/nyinaa/package.d
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
85 changes: 85 additions & 0 deletions source/nyinaa/range.d
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;

0 comments on commit 0ea7503

Please sign in to comment.