Rip out dataspecs everywhere.

This commit is contained in:
David Given
2021-05-18 22:22:06 +02:00
parent 9df35c1814
commit a0164b8de3
30 changed files with 104 additions and 435 deletions

42
lib/bitmap.cc Normal file
View File

@@ -0,0 +1,42 @@
#include "globals.h"
#include "flags.h"
#include "fmt/format.h"
#include "dep/agg/include/agg2d.h"
#include "dep/stb/stb_image_write.h"
#include "utils.h"
#include "bitmap.h"
#include <regex>
#include <sstream>
Bitmap::Bitmap(const std::string filename, unsigned width, unsigned height):
filename(filename),
width(width),
height(height),
initialised(true)
{}
Agg2D& Bitmap::painter()
{
if (!_painter)
{
_bitmap.resize(width * height * 4, 255);
_painter.reset(new Agg2D());
_painter->attach(&_bitmap[0], width, height, width*4);
}
return *_painter;
}
void Bitmap::save()
{
if (endsWith(filename, ".png"))
stbi_write_png(filename.c_str(), width, height, 4, &_bitmap[0], width*4);
else if (endsWith(filename, ".bmp"))
stbi_write_bmp(filename.c_str(), width, height, 4, &_bitmap[0]);
else if (endsWith(filename, ".tga"))
stbi_write_tga(filename.c_str(), width, height, 4, &_bitmap[0]);
else if (endsWith(filename, ".jpg"))
stbi_write_jpg(filename.c_str(), width, height, 4, &_bitmap[0], 80);
else
Error() << "don't know how to write that image format";
}

24
lib/bitmap.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef BITMAP_H
#define BITMAP_H
class Agg2D;
class Bitmap
{
public:
Bitmap(const std::string filename, unsigned width, unsigned height);
Agg2D& painter();
void save();
private:
std::vector<uint8_t> _bitmap;
std::unique_ptr<Agg2D> _painter;
public:
std::string filename;
unsigned width;
unsigned height;
bool initialised : 1;
};
#endif

View File

@@ -1,195 +0,0 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "fmt/format.h"
#include "dep/agg/include/agg2d.h"
#include "dep/stb/stb_image_write.h"
#include "utils.h"
#include <regex>
#include <sstream>
MissingModifierException::MissingModifierException(const std::string& mod):
mod(mod),
std::runtime_error(fmt::format("missing mandatory modifier '{}'", mod))
{}
std::vector<std::string> DataSpec::split(
const std::string& s, const std::string& delimiter)
{
std::vector<std::string> ret;
if (!s.empty())
{
size_t start = 0;
size_t end = 0;
size_t len = 0;
do
{
end = s.find(delimiter,start);
len = end - start;
std::string token = s.substr(start, len);
ret.emplace_back( token );
start += len + delimiter.length();
}
while (end != std::string::npos);
}
return ret;
}
std::set<unsigned> DataSpec::parseRange(const std::string& data)
{
static const std::regex DATA_REGEX("([0-9]+)(?:(?:-([0-9]+))|(?:\\+([0-9]+)))?(?:x([0-9]+))?");
std::set<unsigned> result;
for (auto& data : split(data, ","))
{
int start = 0;
int count = 1;
int step = 1;
std::smatch dmatch;
if (!std::regex_match(data, dmatch, DATA_REGEX))
Error() << "invalid data in mod '" << data << "'";
start = std::stoi(dmatch[1]);
if (!dmatch[2].str().empty())
count = std::stoi(dmatch[2]) - start + 1;
if (!dmatch[3].str().empty())
count = std::stoi(dmatch[3]);
if (!dmatch[4].str().empty())
step = std::stoi(dmatch[4]);
if (count < 0)
Error() << "mod '" << data << "' specifies an illegal quantity";
for (int i = start; i < (start+count); i += step)
result.insert(i);
}
return result;
}
DataSpec::Modifier DataSpec::parseMod(const std::string& spec)
{
static const std::regex MOD_REGEX("([a-z]*)=([-x+0-9,]*)");
std::smatch match;
if (!std::regex_match(spec, match, MOD_REGEX))
Error() << "invalid data modifier syntax '" << spec << "'";
Modifier m;
m.name = match[1];
m.source = spec;
m.data = parseRange(match[2]);
return m;
}
void DataSpec::set(const std::string& spec)
{
std::vector<std::string> words = split(spec, ":");
if (words.size() == 0)
return;
filename = words[0];
if (words.size() > 1)
{
for (size_t i = 1; i < words.size(); i++)
{
auto mod = parseMod(words[i]);
modifiers[mod.name] = mod;
}
}
}
const DataSpec::Modifier& DataSpec::at(const std::string& mod) const
{
try
{
return modifiers.at(mod);
}
catch (const std::out_of_range& e)
{
throw MissingModifierException(mod);
}
}
bool DataSpec::has(const std::string& mod) const
{
return modifiers.find(mod) != modifiers.end();
}
DataSpec::operator std::string(void) const
{
std::stringstream ss;
ss << filename;
for (const auto& mod : modifiers)
ss << ':' << mod.second.source;
return ss.str();
}
BitmapSpec::BitmapSpec(const DataSpec& spec)
{
try
{
filename = spec.filename;
if (!spec.has("w") && !spec.has("h"))
{
width = height = 0;
initialised = false;
}
else
{
width = spec.at("w").only();
height = spec.at("h").only();
initialised = true;
}
}
catch (const MissingModifierException& e)
{
Error() << e.what() << " in imagespec '" << spec << "'";
}
for (const auto& e : spec.modifiers)
{
const auto name = e.second.name;
if ((name != "w") && (name != "h"))
Error() << fmt::format("unknown fluxspec modifier '{}'", name);
}
}
BitmapSpec::BitmapSpec(const std::string filename, unsigned width, unsigned height):
filename(filename),
width(width),
height(height),
initialised(true)
{}
Agg2D& BitmapSpec::painter()
{
if (!_painter)
{
_bitmap.resize(width * height * 4, 255);
_painter.reset(new Agg2D());
_painter->attach(&_bitmap[0], width, height, width*4);
}
return *_painter;
}
void BitmapSpec::save()
{
if (endsWith(filename, ".png"))
stbi_write_png(filename.c_str(), width, height, 4, &_bitmap[0], width*4);
else if (endsWith(filename, ".bmp"))
stbi_write_bmp(filename.c_str(), width, height, 4, &_bitmap[0]);
else if (endsWith(filename, ".tga"))
stbi_write_tga(filename.c_str(), width, height, 4, &_bitmap[0]);
else if (endsWith(filename, ".jpg"))
stbi_write_jpg(filename.c_str(), width, height, 4, &_bitmap[0], 80);
else
Error() << "don't know how to write that image format";
}

View File

@@ -1,135 +0,0 @@
#ifndef DATASPEC_H
#define DATASPEC_H
class MissingModifierException : public std::runtime_error
{
public:
MissingModifierException(const std::string& mod);
const std::string mod;
};
class DataSpec
{
public:
struct Modifier
{
std::string name;
std::set<unsigned> data;
std::string source;
bool operator == (const Modifier& other) const
{ return (name == other.name) && (data == other.data); }
bool operator != (const Modifier& other) const
{ return (name != other.name) || (data != other.data); }
unsigned only() const
{
if (data.size() != 1)
Error() << "modifier " << name << " can only have one value";
return *(data.begin());
}
};
public:
static std::vector<std::string> split(
const std::string& s, const std::string& delimiter);
static std::set<unsigned> parseRange(const std::string& spec);
static Modifier parseMod(const std::string& spec);
public:
DataSpec(const std::string& spec)
{ set(spec); }
void set(const std::string& spec);
operator std::string () const;
const Modifier& at(const std::string& mod) const;
bool has(const std::string& mod) const;
unsigned atOr(const std::string& mod, unsigned value) const
{ return has(mod) ? at(mod).only() : value; }
std::string filename;
std::map<std::string, Modifier> modifiers;
};
static inline std::ostream& operator << (std::ostream& os, const DataSpec& dataSpec)
{ os << (std::string)dataSpec; return os; }
class DataSpecFlag : public Flag
{
public:
DataSpecFlag(const std::vector<std::string>& names, const std::string helptext,
const std::string& defaultValue):
Flag(names, helptext),
_value(defaultValue)
{}
const DataSpec& get() const
{ checkInitialised(); return _value; }
operator const DataSpec& () const
{ return get(); }
bool hasArgument() const { return true; }
const std::string defaultValueAsString() const { return _value; }
void set(const std::string& value) { _value.set(value); }
private:
DataSpec _value;
};
class RangeFlag : public Flag
{
public:
RangeFlag(const std::vector<std::string>& names, const std::string helptext,
const std::string& defaultValue):
Flag(names, helptext),
_stringValue(defaultValue),
_value(DataSpec::parseRange(defaultValue))
{}
const std::set<unsigned>& get() const
{ checkInitialised(); return _value; }
operator const std::set<unsigned>& () const
{ return get(); }
bool hasArgument() const { return true; }
const std::string defaultValueAsString() const { return _stringValue; }
void set(const std::string& value)
{
_stringValue = value;
_value = DataSpec::parseRange(value);
}
private:
std::string _stringValue;
std::set<unsigned> _value;
};
class Agg2D;
class BitmapSpec
{
public:
BitmapSpec(const DataSpec& dataSpec);
BitmapSpec(const std::string filename, unsigned width, unsigned height);
Agg2D& painter();
void save();
private:
std::vector<uint8_t> _bitmap;
std::unique_ptr<Agg2D> _painter;
public:
std::string filename;
unsigned width;
unsigned height;
bool initialised : 1;
};
#endif

View File

@@ -4,7 +4,6 @@
#include "sql.h"
#include "bytes.h"
#include "protocol.h"
#include "dataspec.h"
#include "fluxsink/fluxsink.h"
#include "decoders/fluxmapreader.h"
#include "lib/fluxsink/fluxsink.pb.h"

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "fluxsink/fluxsink.h"
#include "lib/config.pb.h"
#include "proto.h"

View File

@@ -4,7 +4,6 @@
#include "sql.h"
#include "bytes.h"
#include "protocol.h"
#include "dataspec.h"
#include "fluxsink/fluxsink.h"
#include "decoders/fluxmapreader.h"
#include "lib/fluxsink/fluxsink.pb.h"

View File

@@ -4,7 +4,6 @@
#include "sql.h"
#include "bytes.h"
#include "protocol.h"
#include "dataspec.h"
#include "fluxsink/fluxsink.h"
#include "decoders/fluxmapreader.h"
#include "lib/fluxsink/fluxsink.pb.h"

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "fluxsource/fluxsource.h"
#include "lib/config.pb.h"
#include "proto.h"

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagereader/imagereader.h"

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagereader/imagereader.h"

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagereader/imagereader.h"

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagereader/imagereader.h"

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagereader/imagereader.h"

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagewriter/imagewriter.h"

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagewriter/imagewriter.h"

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagewriter/imagewriter.h"

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagewriter/imagewriter.h"

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagewriter/imagewriter.h"

View File

@@ -6,7 +6,6 @@
#include "reader.h"
#include "fluxmap.h"
#include "sql.h"
#include "dataspec.h"
#include "decoders/decoders.h"
#include "sector.h"
#include "sectorset.h"

View File

@@ -5,7 +5,6 @@
#include "sql.h"
#include "protocol.h"
#include "usb/usb.h"
#include "dataspec.h"
#include "encoders/encoders.h"
#include "fluxsource/fluxsource.h"
#include "fluxsink/fluxsink.h"

View File

@@ -304,10 +304,10 @@ buildlibrary libbackend.a \
arch/tids990/encoder.cc \
arch/victor9k/decoder.cc \
arch/zilogmcz/decoder.cc \
lib/bitmap.cc \
lib/bytes.cc \
lib/crc.cc \
lib/csvreader.cc \
lib/dataspec.cc \
lib/decoders/decoders.cc \
lib/decoders/fluxmapreader.cc \
lib/decoders/fmmfm.cc \
@@ -465,7 +465,6 @@ runtest bitaccumulator-test tests/bitaccumulator.cc
runtest bytes-test tests/bytes.cc
runtest compression-test tests/compression.cc
runtest csvreader-test tests/csvreader.cc
runtest dataspec-test tests/dataspec.cc
runtest flags-test tests/flags.cc
runtest fluxpattern-test tests/fluxpattern.cc
runtest fmmfm-test tests/fmmfm.cc

View File

@@ -1,7 +1,7 @@
#include "globals.h"
#include "flags.h"
#include "usb/usb.h"
#include "dataspec.h"
#include "bitmap.h"
#include "fluxmap.h"
#include "decoders/fluxmapreader.h"
#include "writer.h"
@@ -53,10 +53,20 @@ static StringFlag writeCsv(
"Write detailed CSV data",
"");
static DataSpecFlag writeImg(
static StringFlag writeImg(
{ "--write-img" },
"Draw a graph of the response data",
":w=640:h=480");
"analysis.png");
static IntFlag imgWidth(
{ "--width" },
"Width of output graph",
800);
static IntFlag imgHeight(
{ "--height" },
"Height of output graph",
600);
/* This is the Turbo colourmap.
* https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html
@@ -286,16 +296,16 @@ int mainAnalyseDriveResponse(int argc, const char* argv[])
}
}
BitmapSpec bitmapSpec(writeImg);
if (!bitmapSpec.filename.empty())
Bitmap bitmap(writeImg, imgWidth, imgHeight);
if (!bitmap.filename.empty())
{
Agg2D& painter = bitmapSpec.painter();
Agg2D& painter = bitmap.painter();
painter.clearAll(0xdd, 0xdd, 0xdd);
const double MARGIN = 30;
agg::rect_d drawableBounds = {
MARGIN*1.5, MARGIN,
bitmapSpec.width - MARGIN, bitmapSpec.height - MARGIN
bitmap.width - MARGIN, bitmap.height - MARGIN
};
agg::rect_d colourbarBounds = {
drawableBounds.x2 - MARGIN, drawableBounds.y1,
@@ -347,7 +357,7 @@ int mainAnalyseDriveResponse(int argc, const char* argv[])
painter.lineColor(0, 0, 0);
painter.rectangle(graphBounds.x1, graphBounds.y1, graphBounds.x2, graphBounds.y2);
painter.rectangle(colourbarBounds.x1, drawableBounds.y1, drawableBounds.x2, drawableBounds.y2);
bitmapSpec.save();
bitmap.save();
}
return 0;

View File

@@ -1,7 +1,7 @@
#define _USE_MATH_DEFINES
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "bitmap.h"
#include "fluxmap.h"
#include "sector.h"
#include "sectorset.h"
@@ -20,10 +20,20 @@ static StringFlag source(
"CSV file produced by reader",
"");
static DataSpecFlag writeImg(
static StringFlag writeImg(
{ "--img", "-o" },
"Draw a graph of the disk layout",
":w=800:h=600");
"disklayout.png");
static IntFlag imgWidth(
{ "--width" },
"Width of output graph",
800);
static IntFlag imgHeight(
{ "--height" },
"Height of output graph",
600);
static IntFlag period(
{ "--visualiser-period" },
@@ -47,16 +57,16 @@ static const int TRACKS = 83;
void visualiseSectorsToFile(const SectorSet& sectors, const std::string& filename)
{
BitmapSpec bitmapSpec(writeImg);
if (bitmapSpec.filename.empty())
Bitmap bitmap(writeImg, imgWidth, imgHeight);
if (bitmap.filename.empty())
Error() << "you must specify an image filename to write to";
Agg2D& painter = bitmapSpec.painter();
Agg2D& painter = bitmap.painter();
painter.clearAll(0xff, 0xff, 0xff);
const double radians_per_ns = 2.0*M_PI / (period*1e6);
const double available_width = bitmapSpec.width;
const double available_height = bitmapSpec.height;
const double available_width = bitmap.width;
const double available_height = bitmap.height;
const double panel_centre = (sideToDraw == -1)
? (available_width / 4)
: (available_width / 2);
@@ -152,7 +162,7 @@ void visualiseSectorsToFile(const SectorSet& sectors, const std::string& filenam
break;
}
bitmapSpec.save();
bitmap.save();
}
static void check_for_error()

View File

@@ -1,6 +1,5 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include "sector.h"
#include "sectorset.h"
#include "imagereader/imagereader.h"

View File

@@ -8,7 +8,6 @@
#include "sectorset.h"
#include "record.h"
#include "proto.h"
#include "dataspec.h"
#include "fluxsink/fluxsink.h"
#include "fluxsource/fluxsource.h"
#include "arch/brother/brother.h"

View File

@@ -8,7 +8,6 @@
#include "sectorset.h"
#include "record.h"
#include "proto.h"
#include "dataspec.h"
#include "fluxsource/fluxsource.h"
#include "fluxsink/fluxsink.h"
#include "arch/brother/brother.h"

View File

@@ -1,7 +1,6 @@
#include "globals.h"
#include "flags.h"
#include "usb/usb.h"
#include "dataspec.h"
#include "protocol.h"
#include "proto.h"

View File

@@ -8,7 +8,6 @@
#include "sectorset.h"
#include "record.h"
#include "proto.h"
#include "dataspec.h"
#include "fluxsink/fluxsink.h"
#include "arch/brother/brother.h"
#include "arch/ibm/ibm.h"

View File

@@ -1,64 +0,0 @@
#include "globals.h"
#include "flags.h"
#include "dataspec.h"
#include <assert.h>
static void test_split(void)
{
assert((DataSpec::split("1,2,3", ",")
== std::vector<std::string>{"1", "2", "3"}));
assert((DataSpec::split(",2,3", ",")
== std::vector<std::string>{"", "2", "3"}));
assert((DataSpec::split(",2,", ",")
== std::vector<std::string>{"", "2", ""}));
assert((DataSpec::split("2", ",")
== std::vector<std::string>{"2"}));
assert((DataSpec::split("", ",")
== std::vector<std::string>()));
}
static void test_parserange(void)
{
assert(DataSpec::parseRange("")
== std::set<unsigned>());
assert(DataSpec::parseRange("1")
== std::set<unsigned>({1}));
assert(DataSpec::parseRange("1,3,5")
== std::set<unsigned>({1, 3, 5}));
assert(DataSpec::parseRange("1,1,1")
== std::set<unsigned>({1}));
assert(DataSpec::parseRange("2-3")
== std::set<unsigned>({2, 3}));
assert(DataSpec::parseRange("2+3")
== std::set<unsigned>({2, 3, 4}));
assert(DataSpec::parseRange("2+3x2")
== std::set<unsigned>({2, 4}));
assert(DataSpec::parseRange("9,2+3x2")
== std::set<unsigned>({2, 4, 9}));
}
static void test_parsemod(void)
{
assert(DataSpec::parseMod("x=1")
== (DataSpec::Modifier{"x", {1}}));
assert(DataSpec::parseMod("x=1,3,5")
== (DataSpec::Modifier{"x", {1, 3, 5}}));
assert(DataSpec::parseMod("x=1,1,1")
== (DataSpec::Modifier{"x", {1}}));
assert(DataSpec::parseMod("x=2-3")
== (DataSpec::Modifier{"x", {2, 3}}));
assert(DataSpec::parseMod("x=2+3")
== (DataSpec::Modifier{"x", {2, 3, 4}}));
assert(DataSpec::parseMod("x=2+3x2")
== (DataSpec::Modifier{"x", {2, 4}}));
assert(DataSpec::parseMod("x=9,2+3x2")
== (DataSpec::Modifier{"x", {2, 4, 9}}));
}
int main(int argc, const char* argv[])
{
test_split();
test_parserange();
test_parsemod();
return 0;
}