diff --git a/.gitignore b/.gitignore index 80152a5..05fe5a5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ build .yotta.json yotta_modules yotta_targets +*.swp +Makefile diff --git a/inc/ManagedString.h b/inc/ManagedString.h index 81c06bf..bf2017c 100644 --- a/inc/ManagedString.h +++ b/inc/ManagedString.h @@ -1,7 +1,14 @@ #ifndef MANAGED_STRING_H #define MANAGED_STRING_H -#include "mbed.h" +#include "RefCounted.h" + +struct StringData : RefCounted +{ + uint16_t len; + char data[0]; +}; + /** * Class definition for a ManagedString. @@ -14,21 +21,43 @@ * 1) std::shared_ptr is not yet availiable on the ARMCC compiler * 2) to reduce memory footprint - we don't need many of the other features in the std library * 3) it makes an interestin case study for anyone interested in seeing how it works! + * 4) we need explicit reference counting to inter-op with low-level application langauge runtimes + * 5) the reference counting needs to also work for read-only, flash-resident strings */ class ManagedString { - // Internally we record the string as a char *, but control access to this to proide immutability - // and reference counting. - char *data; - int16_t *ref; - int16_t len; + // StringData contains the reference count, the length, follwed by char[] data, all in one block. + // When referece count is 0xffff, then it's read only and should not be counted. + // Otherwise the block was malloc()ed. + // We control access to this to proide immutability and reference counting. + StringData *ptr; public: + /** + * Constructor. + * Create a managed string from a specially prepared string literal. It will ptr->incr(). + * + * @param ptr The literal - first two bytes should be 0xff, then the length in little endian, then the literal. The literal has to be 4-byte aligned. + * + * Example: + * @code + * static const char hello[] __attribute__ ((aligned (4))) = "\xff\xff\x05\x00" "Hello"; + * ManagedString s((StringData*)(void*)hello); + * @endcode + */ + ManagedString(StringData *ptr); + + /** + * Get current ptr, do not decr() it, and set the current instance to empty string. + * This is to be used by specialized runtimes which pass StringData around. + */ + StringData *leakData(); + /** * Constructor. * Create a managed string from a pointer to an 8-bit character buffer. - * The buffer is copied to ensure sage memory management (the supplied + * The buffer is copied to ensure safe memory management (the supplied * character buffer may be decalred on the stack for instance). * * @param str The character array on which to base the new ManagedString. @@ -115,7 +144,7 @@ class ManagedString * * Free this ManagedString, and decrement the reference count to the * internal character buffer. If we're holding the last reference, - * also free the character buffer and reference counter. + * also free the character buffer. */ ~ManagedString(); @@ -251,11 +280,14 @@ class ManagedString /** - * Provides an immutable 8 bit wide haracter buffer representing this string. + * Provides an immutable 8 bit wide character buffer representing this string. * * @return a pointer to the character buffer. */ - const char *toCharArray(); + const char *toCharArray() const + { + return ptr->data; + } /** * Determines the length of this ManagedString in characters. @@ -269,7 +301,10 @@ class ManagedString * print(s.length()) // prints "4" * @endcode */ - int16_t length(); + int16_t length() const + { + return ptr->len; + } /** * Empty String constant diff --git a/inc/MicroBitImage.h b/inc/MicroBitImage.h index bb5af02..26aabea 100644 --- a/inc/MicroBitImage.h +++ b/inc/MicroBitImage.h @@ -2,6 +2,14 @@ #define MICROBIT_IMAGE_H #include "mbed.h" +#include "RefCounted.h" + +struct ImageData : RefCounted +{ + uint16_t width; // Width in pixels + uint16_t height; // Height in pixels + uint8_t data[0]; // 2D array representing the bitmap image +}; /** * Class definition for a MicroBitImage. @@ -11,9 +19,7 @@ */ class MicroBitImage { - int16_t width; // Width of the bitmap, in pixels. - int16_t height; // Height of the bitmap, in pixels. - int16_t *ref; // Reference count. + ImageData *ptr; // Pointer to payload data /** @@ -32,7 +38,34 @@ class MicroBitImage public: static MicroBitImage EmptyImage; // Shared representation of a null image. - uint8_t *bitmap; // 2D array representing the bitmap image. + + /** + * Get current ptr, do not decr() it, and set the current instance to empty image. + * This is to be used by specialized runtimes which pass ImageData around. + */ + ImageData *leakData(); + + /** + * Return a 2D array representing the bitmap image. + */ + uint8_t *getBitmap() + { + return ptr->data; + } + + /** + * Constructor. + * Create an image from a specially prepared constant array, with no copying. Will call ptr->incr(). + * + * @param ptr The literal - first two bytes should be 0xff, then width, 0, height, 0, and the bitmap. Width and height are 16 bit. The literal has to be 4-byte aligned. + * + * Example: + * @code + * static const uint8_t heart[] __attribute__ ((aligned (4))) = { 0xff, 0xff, 10, 0, 5, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i((ImageData*)(void*)heart); + * @endcode + */ + MicroBitImage(ImageData *ptr); /** * Default Constructor. @@ -325,7 +358,10 @@ class MicroBitImage * i.getWidth(); //equals 10... * @endcode */ - int getWidth(); + int getWidth() const + { + return ptr->width; + } /** * Gets the height of this image. @@ -339,8 +375,28 @@ class MicroBitImage * i.getHeight(); //equals 5... * @endcode */ - int getHeight(); + int getHeight() const + { + return ptr->height; + } + /** + * Gets number of bytes in the bitmap, ie., width * height. + * + * @return The size of the bitmap. + * + * Example: + * @code + * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i(10,5,heart); + * i.getSize(); //equals 50... + * @endcode + */ + int getSize() const + { + return ptr->width * ptr->height; + } + /** * Converts the bitmap to a csv string. * @@ -352,7 +408,7 @@ class MicroBitImage * @endcode */ ManagedString toString(); - + /** * Crops the image to the given dimensions * @@ -372,6 +428,17 @@ class MicroBitImage */ MicroBitImage crop(int startx, int starty, int finx, int finy); + /** + * Check if image is read-only (i.e., residing in flash). + */ + bool isReadOnly(); + + /** + * Create a copy of the image bitmap. Used particularly, when isReadOnly() is true. + * + * @return an instance of MicroBitImage which can be modified independently of the current instance + */ + MicroBitImage clone(); }; #endif diff --git a/inc/RefCounted.h b/inc/RefCounted.h new file mode 100644 index 0000000..9d332ad --- /dev/null +++ b/inc/RefCounted.h @@ -0,0 +1,34 @@ +#ifndef REF_COUNTED_H +#define REF_COUNTED_H + +#include "mbed.h" + +/** + * Base class for payload for ref-counted objects. Used by ManagedString and MicroBitImage. + * There is no constructor, as this struct is typically malloc()ed. + */ +struct RefCounted +{ +public: + /** + * The high 15 bits hold the number of outstanding references. The lowest bit is always 1 + * to make sure it doesn't look like vtable. + * Should never be even or one (object should be deleted then). + * When it's set to 0xffff, it means the object sits in flash and should not be counted. + */ + uint16_t refCount; + + /** Increment reference count. */ + void incr(); + + /** Decrement reference count. */ + void decr(); + + /** Initializes for one outstanding reference. */ + void init(); + + /** Checks if the object sits in flash memory. */ + bool isReadOnly(); +}; + +#endif diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 8d16619..47faf72 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -28,6 +28,7 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES "MicroBitSerial.cpp" "MicroBitHeapAllocator.cpp" "MicroBitListener.cpp" + "RefCounted.cpp" "MemberFunctionCallback.cpp" "ble-services/MicroBitDFUService.cpp" "ble-services/MicroBitEventService.cpp" diff --git a/source/ManagedString.cpp b/source/ManagedString.cpp index 829b65a..e200ce0 100644 --- a/source/ManagedString.cpp +++ b/source/ManagedString.cpp @@ -3,6 +3,7 @@ #include "mbed.h" #include "MicroBit.h" +static const char empty[] __attribute__ ((aligned (4))) = "\xff\xff\0\0\0"; /** * Internal constructor helper. @@ -10,11 +11,7 @@ */ void ManagedString::initEmpty() { - data = ManagedString::EmptyString.data; - ref = ManagedString::EmptyString.ref; - len = ManagedString::EmptyString.len; - - (*ref)++; + ptr = (StringData*)(void*)empty; } /** @@ -25,13 +22,41 @@ void ManagedString::initString(const char *str) { // Initialise this ManagedString as a new string, using the data provided. // We assume the string is sane, and null terminated. - len = strlen(str); - data = (char *) malloc(len+1); - memcpy(data, str, len+1); - ref = (int16_t *) malloc(sizeof(int16_t)); - *ref = 1; + int len = strlen(str); + ptr = (StringData *) malloc(4+len+1); + ptr->init(); + ptr->len = len; + memcpy(ptr->data, str, len+1); } +/** + * Constructor. + * Create a managed string from a specially prepared string literal. It will ptr->incr(). + * + * @param ptr The literal - first two bytes should be 0xff, then the length in little endian, then the literal. The literal has to be 4-byte aligned. + * + * Example: + * @code + * static const char hello[] __attribute__ ((aligned (4))) = "\xff\xff\x05\x00" "Hello"; + * ManagedString s((StringData*)(void*)hello); + * @endcode + */ +ManagedString::ManagedString(StringData *p) +{ + ptr = p; + ptr->incr(); +} + +/** + * Get current ptr, do not decr() it, and set the current instance to empty string. + * This is to be used by specialized runtimes which pass StringData around. + */ +StringData* ManagedString::leakData() +{ + StringData *res = ptr; + initEmpty(); + return res; +} /** * Constructor. @@ -65,7 +90,6 @@ ManagedString::ManagedString(const int value) */ ManagedString::ManagedString(const char value) { - char str[2] = {value, 0}; initString(str); } @@ -82,7 +106,7 @@ ManagedString::ManagedString(const char value) ManagedString::ManagedString(const char *str) { // Sanity check. Return EmptyString for anything distasteful - if ((str == NULL || *str == 0) && this != &ManagedString::EmptyString) + if (str == NULL || *str == 0) { initEmpty(); return; @@ -94,19 +118,17 @@ ManagedString::ManagedString(const char *str) ManagedString::ManagedString(const ManagedString &s1, const ManagedString &s2) { // Calculate length of new string. - len = s1.len + s2.len; + int len = s1.length() + s2.length(); // Create a new buffer for holding the new string data. - data = (char *) malloc(len+1); + ptr = (StringData*) malloc(4+len+1); + ptr->init(); + ptr->len = len; // Enter the data, and terminate the string. - memcpy(data, s1.data, s1.len); - memcpy(data + s1.len, s2.data, s2.len); - data[len] = 0; - - // Initialise the ref count and we're done. - ref = (int16_t *) malloc(sizeof(int16_t)); - *ref = 1; + memcpy(ptr->data, s1.toCharArray(), s1.length()); + memcpy(ptr->data + s1.length(), s2.toCharArray(), s2.length()); + ptr->data[len] = 0; } @@ -133,17 +155,14 @@ ManagedString::ManagedString(const char *str, const int16_t length) return; } - // Store the length of the new string - len = length; // Allocate a new buffer, and create a NULL terminated string. - data = (char *) malloc(len+1); - memcpy(data, str, len); - data[len] = 0; - - // Initialize a refcount and we're done. - ref = (int16_t *) malloc(sizeof(int16_t)); - *ref = 1; + ptr = (StringData*) malloc(4+length+1); + ptr->init(); + // Store the length of the new string + ptr->len = length; + memcpy(ptr->data, str, length); + ptr->data[length] = 0; } /** @@ -161,11 +180,8 @@ ManagedString::ManagedString(const char *str, const int16_t length) */ ManagedString::ManagedString(const ManagedString &s) { - data = s.data; - ref = s.ref; - len = s.len; - - (*ref)++; + ptr = s.ptr; + ptr->incr(); } @@ -193,11 +209,7 @@ ManagedString::ManagedString() */ ManagedString::~ManagedString() { - if(--(*ref) == 0) - { - free(data); - free(ref); - } + ptr->decr(); } /** @@ -220,19 +232,12 @@ ManagedString::~ManagedString() */ ManagedString& ManagedString::operator = (const ManagedString& s) { - if(this == &s) + if (this->ptr == s.ptr) return *this; - if(--(*ref) == 0) - { - free(data); - free(ref); - } - - data = s.data; - ref = s.ref; - len = s.len; - (*ref)++; + ptr->decr(); + ptr = s.ptr; + ptr->incr(); return *this; } @@ -258,7 +263,7 @@ ManagedString& ManagedString::operator = (const ManagedString& s) */ bool ManagedString::operator== (const ManagedString& s) { - return ((len == s.len) && (memcmp(data,s.data,len)==0)); + return ((length() == s.length()) && (strcmp(toCharArray(),s.toCharArray())==0)); } /** @@ -282,7 +287,7 @@ bool ManagedString::operator== (const ManagedString& s) */ bool ManagedString::operator< (const ManagedString& s) { - return (memcmp(data, s.data,min(len,s.len))<0); + return (strcmp(toCharArray(), s.toCharArray())<0); } /** @@ -306,7 +311,7 @@ bool ManagedString::operator< (const ManagedString& s) */ bool ManagedString::operator> (const ManagedString& s) { - return (memcmp(data, s.data,min(len,s.len))>0); + return (strcmp(toCharArray(), s.toCharArray())>0); } /** @@ -326,14 +331,14 @@ bool ManagedString::operator> (const ManagedString& s) ManagedString ManagedString::substring(int16_t start, int16_t length) { // If the parameters are illegal, just return a reference to the empty string. - if (start >= len) + if (start >= this->length()) return ManagedString(ManagedString::EmptyString); // Compute a safe copy length; - length = min(len-start, length); + length = min(this->length()-start, length); // Build a ManagedString from this. - return ManagedString(data+start, length); + return ManagedString(toCharArray()+start, length); } /** @@ -353,19 +358,13 @@ ManagedString ManagedString::substring(int16_t start, int16_t length) ManagedString ManagedString::operator+ (ManagedString& s) { // If the other string is empty, nothing to do! - if(s.len == 0) + if(s.length() == 0) return *this; - if (len == 0) - return s; - - if(s == ManagedString::EmptyString) - return *this; - - if(*this == ManagedString::EmptyString) + if (length() == 0) return s; - return ManagedString(data, s.data); + return ManagedString(*this, s); } @@ -384,39 +383,10 @@ ManagedString ManagedString::operator+ (ManagedString& s) */ char ManagedString::charAt(int16_t index) { - return (index >=0 && index < len) ? data[index] : 0; -} - -/** - * Provides an immutable 8 bit wide haracter buffer representing this string. - * - * @return a pointer to the charcter buffer. - */ -const char *ManagedString::toCharArray() -{ - return data; -} - -/** - * Determines the length of this ManagedString in characters. - * - * @return the length of the string in characters. - * - * Example: - * @code - * ManagedString s("abcd"); - * - * print(s.length()) // prints "4" - * @endcode - */ -int16_t ManagedString::length() -{ - return len; + return (index >=0 && index < length()) ? ptr->data[index] : 0; } /** * Empty string constant literal */ -ManagedString ManagedString::EmptyString("\0"); - - +ManagedString ManagedString::EmptyString((StringData*)(void*)empty); diff --git a/source/MicroBit.cpp b/source/MicroBit.cpp index ed8d38d..b3367b2 100644 --- a/source/MicroBit.cpp +++ b/source/MicroBit.cpp @@ -122,7 +122,9 @@ void MicroBit::init() #endif #if CONFIG_ENABLED(MICROBIT_BLE_DEVICE_INFORMATION_SERVICE) - DeviceInformationService ble_device_information_service (*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, getSerial().toCharArray(), MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION); + // Create a temporary, so that compiler doesn't delete the pointer before DeviceInformationService copies it + ManagedString tmp = getSerial(); + DeviceInformationService ble_device_information_service (*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, tmp.toCharArray(), MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION); #endif #if CONFIG_ENABLED(MICROBIT_BLE_EVENT_SERVICE) @@ -233,7 +235,7 @@ ManagedString MicroBit::getSerial() ManagedString s1 = ManagedString(n1); ManagedString s2 = ManagedString(n2); - return s1+s2; + return s1 + s2; } /** diff --git a/source/MicroBitDisplay.cpp b/source/MicroBitDisplay.cpp index 1337071..1513c10 100644 --- a/source/MicroBitDisplay.cpp +++ b/source/MicroBitDisplay.cpp @@ -126,7 +126,7 @@ void MicroBitDisplay::render() y = height - 1 - t; } - if(image.bitmap[y*(width*2)+x]) + if(image.getBitmap()[y*(width*2)+x]) coldata |= (1 << i); } @@ -175,7 +175,7 @@ void MicroBitDisplay::renderGreyscale() y = height - 1 - t; } - if(min(image.bitmap[y * (width * 2) + x],brightness) & greyscaleBitMsk) + if(min(image.getBitmap()[y * (width * 2) + x],brightness) & greyscaleBitMsk) coldata |= (1 << i); } //write the new bit pattern diff --git a/source/MicroBitHeapAllocator.cpp b/source/MicroBitHeapAllocator.cpp index 36cf1d0..965e893 100644 --- a/source/MicroBitHeapAllocator.cpp +++ b/source/MicroBitHeapAllocator.cpp @@ -77,7 +77,7 @@ void microbit_heap_print(HeapDefinition &heap) while (block < heap.heap_end) { blockSize = *block & ~MICROBIT_HEAP_BLOCK_FREE; - uBit.serialpc.printf("[%C:%d] ", *block & MICROBIT_HEAP_BLOCK_FREE ? 'F' : 'U', blockSize*4); + uBit.serial.printf("[%C:%d] ", *block & MICROBIT_HEAP_BLOCK_FREE ? 'F' : 'U', blockSize*4); if (cols++ == 20) { uBit.serial.printf("\n"); @@ -329,7 +329,7 @@ void *microbit_malloc(size_t size) if (p != NULL) { #if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) - pc.uBit.serial("microbit_malloc: ALLOCATED: %d [%p]\n", size, p); + uBit.serial.printf("microbit_malloc: ALLOCATED: %d [%p]\n", size, p); #endif return p; } @@ -345,7 +345,7 @@ void *microbit_malloc(size_t size) #if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) // Keep everything trasparent if we've not been initialised yet if (microbit_active_heaps()) - pc.uBit.serial("microbit_malloc: NATIVE ALLOCATED: %d [%p]\n", size, p); + uBit.serial.printf("microbit_malloc: NATIVE ALLOCATED: %d [%p]\n", size, p); #endif return p; } @@ -354,7 +354,7 @@ void *microbit_malloc(size_t size) #if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) // Keep everything trasparent if we've not been initialised yet if (microbit_active_heaps()) - pc.uBit.serial("microbit_malloc: OUT OF MEMORY\n"); + uBit.serial.printf("microbit_malloc: OUT OF MEMORY\n"); #endif #if CONFIG_ENABLED(MICROBIT_PANIC_HEAP_FULL) @@ -375,7 +375,7 @@ void microbit_free(void *mem) #if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG) if (microbit_active_heaps()) - pc.uBit.serial("microbit_free: %p\n", mem); + uBit.serial.printf("microbit_free: %p\n", mem); #endif // Sanity check. if (memory == NULL) diff --git a/source/MicroBitImage.cpp b/source/MicroBitImage.cpp index 43a8a93..686450e 100644 --- a/source/MicroBitImage.cpp +++ b/source/MicroBitImage.cpp @@ -7,10 +7,12 @@ #include "MicroBit.h" + /* * The null image. We actally create a small one byte buffer here, just to keep NULL pointers out of the equation. */ -MicroBitImage MicroBitImage::EmptyImage(1,1); +static const uint16_t empty[] __attribute__ ((aligned (4))) = { 0xffff, 1, 1, 0, }; +MicroBitImage MicroBitImage::EmptyImage((ImageData*)(void*)empty); /** * Default Constructor. @@ -65,12 +67,8 @@ MicroBitImage::MicroBitImage(const int16_t x, const int16_t y) */ MicroBitImage::MicroBitImage(const MicroBitImage &image) { - bitmap = image.bitmap; - width = image.width; - height = image.height; - ref = image.ref; - - (*ref)++; + ptr = image.ptr; + ptr->incr(); } /** @@ -138,17 +136,12 @@ MicroBitImage::MicroBitImage(const char *s) parseReadPtr++; } - // Store the geomtery. - this->width = width; - this->height = height; - this->bitmap = (uint8_t *) malloc(width * height); - this->ref = (int16_t *) malloc(sizeof(int16_t)); - *ref = 1; + this->init(width, height, NULL); // Second pass: collect the data. parseReadPtr = s; parseWritePtr = parseBuf; - bitmapPtr = this->bitmap; + bitmapPtr = this->getBitmap(); while (*parseReadPtr) { @@ -172,6 +165,36 @@ MicroBitImage::MicroBitImage(const char *s) } } +/** + * Constructor. + * Create an image from a specially prepared constant array, with no copying. Will call ptr->incr(). + * + * @param ptr The literal - first two bytes should be 0xff, then width, 0, height, 0, and the bitmap. Width and height are 16 bit. The literal has to be 4-byte aligned. + * + * Example: + * @code + * static const uint8_t heart[] __attribute__ ((aligned (4))) = { 0xff, 0xff, 10, 0, 5, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart + * MicroBitImage i((ImageData*)(void*)heart); + * @endcode + */ +MicroBitImage::MicroBitImage(ImageData *p) +{ + ptr = p; + ptr->incr(); +} + +/** + * Get current ptr, do not decr() it, and set the current instance to empty image. + * This is to be used by specialized runtimes which pass ImageData around. + */ +ImageData *MicroBitImage::leakData() +{ + ImageData* res = ptr; + init_empty(); + return res; +} + + /** * Constructor. * Create a bitmap representation of a given size, based on a given buffer. @@ -197,11 +220,7 @@ MicroBitImage::MicroBitImage(const int16_t x, const int16_t y, const uint8_t *bi */ MicroBitImage::~MicroBitImage() { - if(--(*ref) == 0) - { - free(bitmap); - free(ref); - } + ptr->decr(); } /** @@ -209,12 +228,7 @@ MicroBitImage::~MicroBitImage() */ void MicroBitImage::init_empty() { - bitmap = MicroBitImage::EmptyImage.bitmap; - width = MicroBitImage::EmptyImage.width; - height = MicroBitImage::EmptyImage.height; - ref = MicroBitImage::EmptyImage.ref; - - (*ref)++; + ptr = (ImageData*)(void*)empty; } /** @@ -232,23 +246,21 @@ void MicroBitImage::init(const int16_t x, const int16_t y, const uint8_t *bitmap init_empty(); return; } + // Create a copy of the array - this->width = x; - this->height = y; + ptr = (ImageData*)malloc(4 + x * y); + ptr->init(); + ptr->width = x; + ptr->height = y; // create a linear buffer to represent the image. We could use a jagged/2D array here, but experimentation // showed this had a negative effect on memory management (heap fragmentation etc). - this->bitmap = (uint8_t *) malloc(width*height); - if (bitmap) this->printImage(x,y,bitmap); else this->clear(); - - ref = (int16_t *) malloc(sizeof(int16_t)); - *ref = 1; } /** @@ -271,21 +283,12 @@ void MicroBitImage::init(const int16_t x, const int16_t y, const uint8_t *bitmap */ MicroBitImage& MicroBitImage::operator = (const MicroBitImage& i) { - if(this == &i) + if(ptr == i.ptr) return *this; - if(--(*ref) == 0) - { - free(bitmap); - free(ref); - } - - bitmap = i.bitmap; - width = i.width; - height = i.height; - ref = i.ref; - - (*ref)++; + ptr->decr(); + ptr = i.ptr; + ptr->incr(); return *this; } @@ -309,10 +312,10 @@ MicroBitImage& MicroBitImage::operator = (const MicroBitImage& i) */ bool MicroBitImage::operator== (const MicroBitImage& i) { - if (bitmap == i.bitmap) + if (ptr == i.ptr) return true; else - return ((width == i.width) && (height == i.height) && (memcmp(bitmap, i.bitmap,width*height)==0)); + return (ptr->width == i.ptr->width && ptr->height == i.ptr->height && (memcmp(getBitmap(), i.ptr->data, getSize())==0)); } @@ -327,7 +330,7 @@ bool MicroBitImage::operator== (const MicroBitImage& i) */ void MicroBitImage::clear() { - memclr(this->bitmap, width*height); + memclr(getBitmap(), getSize()); } /** @@ -346,10 +349,10 @@ void MicroBitImage::clear() int MicroBitImage::setPixelValue(int16_t x , int16_t y, uint8_t value) { //sanity check - if(x >= width || y >= height || x < 0 || y < 0) + if(x >= getWidth() || y >= getHeight() || x < 0 || y < 0) return MICROBIT_INVALID_PARAMETER; - this->bitmap[y*width+x] = value; + this->getBitmap()[y*getWidth()+x] = value; return MICROBIT_OK; } @@ -369,10 +372,10 @@ int MicroBitImage::setPixelValue(int16_t x , int16_t y, uint8_t value) int MicroBitImage::getPixelValue(int16_t x , int16_t y) { //sanity check - if(x >= width || y >= height || x < 0 || y < 0) + if(x >= getWidth() || y >= getHeight() || x < 0 || y < 0) return MICROBIT_INVALID_PARAMETER; - return this->bitmap[y*width+x]; + return this->getBitmap()[y*getWidth()+x]; } /** @@ -403,18 +406,18 @@ int MicroBitImage::printImage(int16_t width, int16_t height, const uint8_t *bitm return MICROBIT_INVALID_PARAMETER; // Calcualte sane start pointer. - pixelsToCopyX = min(width,this->width); - pixelsToCopyY = min(height,this->height); + pixelsToCopyX = min(width,this->getWidth()); + pixelsToCopyY = min(height,this->getHeight()); pIn = bitmap; - pOut = this->bitmap; + pOut = this->getBitmap(); // Copy the image, stride by stride. for (int i=0; iwidth; + pOut += this->getWidth(); } return MICROBIT_OK; @@ -445,21 +448,21 @@ int MicroBitImage::paste(const MicroBitImage &image, int16_t x, int16_t y, uint8 // Sanity check. // We permit writes that overlap us, but ones that are clearly out of scope we can filter early. - if (x >= width || y >= height || x+image.width <= 0 || y+image.height <= 0) + if (x >= getWidth() || y >= getHeight() || x+image.getWidth() <= 0 || y+image.getHeight() <= 0) return 0; //Calculate the number of byte we need to copy in each dimension. - cx = x < 0 ? min(image.width + x, width) : min(image.width, width - x); - cy = y < 0 ? min(image.height + y, height) : min(image.height, height - y); + cx = x < 0 ? min(image.getWidth() + x, getWidth()) : min(image.getWidth(), getWidth() - x); + cy = y < 0 ? min(image.getHeight() + y, getHeight()) : min(image.getHeight(), getHeight() - y); // Calculate sane start pointer. - pIn = image.bitmap; + pIn = image.ptr->data; pIn += (x < 0) ? -x : 0; - pIn += (y < 0) ? -image.width*y : 0; + pIn += (y < 0) ? -image.getWidth()*y : 0; - pOut = bitmap; + pOut = getBitmap(); pOut += (x > 0) ? x : 0; - pOut += (y > 0) ? width*y : 0; + pOut += (y > 0) ? getWidth()*y : 0; // Copy the image, stride by stride // If we want primitive transparecy, we do this byte by byte. @@ -478,8 +481,8 @@ int MicroBitImage::paste(const MicroBitImage &image, int16_t x, int16_t y, uint8 } } - pIn += image.width; - pOut += width; + pIn += image.getWidth(); + pOut += getWidth(); } } else @@ -489,8 +492,8 @@ int MicroBitImage::paste(const MicroBitImage &image, int16_t x, int16_t y, uint8 memcpy(pOut, pIn, cx); pxWritten += cx; - pIn += image.width; - pOut += width; + pIn += image.getWidth(); + pOut += getWidth(); } } @@ -519,7 +522,7 @@ int MicroBitImage::print(char c, int16_t x, int16_t y) MicroBitFont font = uBit.display.getFont(); // Sanity check. Silently ignore anything out of bounds. - if (x >= width || y >= height || c < MICROBIT_FONT_ASCII_START || c > font.asciiEnd) + if (x >= getWidth() || y >= getHeight() || c < MICROBIT_FONT_ASCII_START || c > font.asciiEnd) return MICROBIT_INVALID_PARAMETER; // Paste. @@ -539,8 +542,8 @@ int MicroBitImage::print(char c, int16_t x, int16_t y) // Update our X co-ord write position x1 = x+col; - if (x1 < width && y1 < height) - this->bitmap[y1*width+x1] = (v & (0x10 >> col)) ? 255 : 0; + if (x1 < getWidth() && y1 < getHeight()) + this->getBitmap()[y1*getWidth()+x1] = (v & (0x10 >> col)) ? 255 : 0; } } @@ -563,24 +566,24 @@ int MicroBitImage::print(char c, int16_t x, int16_t y) */ int MicroBitImage::shiftLeft(int16_t n) { - uint8_t *p = bitmap; - int pixels = width-n; + uint8_t *p = getBitmap(); + int pixels = getWidth()-n; if (n <= 0 ) return MICROBIT_INVALID_PARAMETER; - if(n >= width) + if(n >= getWidth()) { clear(); return MICROBIT_OK; } - for (int y = 0; y < height; y++) + for (int y = 0; y < getHeight(); y++) { // Copy, and blank fill the rightmost column. memcpy(p, p+n, pixels); memclr(p+pixels, n); - p += width; + p += getWidth(); } return MICROBIT_OK; @@ -602,24 +605,24 @@ int MicroBitImage::shiftLeft(int16_t n) */ int MicroBitImage::shiftRight(int16_t n) { - uint8_t *p = bitmap; - int pixels = width-n; + uint8_t *p = getBitmap(); + int pixels = getWidth()-n; if (n <= 0) return MICROBIT_INVALID_PARAMETER; - if(n >= width) + if(n >= getWidth()) { clear(); return MICROBIT_OK; } - for (int y = 0; y < height; y++) + for (int y = 0; y < getHeight(); y++) { // Copy, and blank fill the leftmost column. memmove(p+n, p, pixels); memclr(p, n); - p += width; + p += getWidth(); } return MICROBIT_OK; @@ -646,25 +649,25 @@ int MicroBitImage::shiftUp(int16_t n) if (n <= 0 ) return MICROBIT_INVALID_PARAMETER; - if(n >= height) + if(n >= getHeight()) { clear(); return MICROBIT_OK; } - pOut = bitmap; - pIn = bitmap+width*n; + pOut = getBitmap(); + pIn = getBitmap()+getWidth()*n; - for (int y = 0; y < height; y++) + for (int y = 0; y < getHeight(); y++) { // Copy, and blank fill the leftmost column. - if (y < height-n) - memcpy(pOut, pIn, width); + if (y < getHeight()-n) + memcpy(pOut, pIn, getWidth()); else - memclr(pOut, width); + memclr(pOut, getWidth()); - pIn += width; - pOut += width; + pIn += getWidth(); + pOut += getWidth(); } return MICROBIT_OK; @@ -691,63 +694,30 @@ int MicroBitImage::shiftDown(int16_t n) if (n <= 0 ) return MICROBIT_INVALID_PARAMETER; - if(n >= height) + if(n >= getHeight()) { clear(); return MICROBIT_OK; } - pOut = bitmap + width*(height-1); - pIn = pOut - width*n; + pOut = getBitmap() + getWidth()*(getHeight()-1); + pIn = pOut - getWidth()*n; - for (int y = 0; y < height; y++) + for (int y = 0; y < getHeight(); y++) { // Copy, and blank fill the leftmost column. - if (y < height-n) - memcpy(pOut, pIn, width); + if (y < getHeight()-n) + memcpy(pOut, pIn, getWidth()); else - memclr(pOut, width); + memclr(pOut, getWidth()); - pIn -= width; - pOut -= width; + pIn -= getWidth(); + pOut -= getWidth(); } return MICROBIT_OK; } -/** - * Gets the width of this image. - * - * @return The width of this image. - * - * Example: - * @code - * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart - * MicroBitImage i(10,5,heart); - * i.getWidth(); //equals 10... - * @endcode - */ -int MicroBitImage::getWidth() -{ - return width; -} - -/** - * Gets the height of this image. - * - * @return The height of this image. - * - * Example: - * @code - * const uint8_t heart[] = { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, }; // a cute heart - * MicroBitImage i(10,5,heart); - * i.getHeight(); //equals 5... - * @endcode - */ -int MicroBitImage::getHeight() -{ - return height; -} /** * Converts the bitmap to a csv string. @@ -762,14 +732,14 @@ int MicroBitImage::getHeight() ManagedString MicroBitImage::toString() { //width including commans and \n * height - int stringSize = ((width * 2) * height); + int stringSize = getSize() * 2; //plus one for string terminator char parseBuffer[stringSize + 1]; parseBuffer[stringSize] = '\0'; - uint8_t *bitmapPtr = bitmap; + uint8_t *bitmapPtr = getBitmap(); int parseIndex = 0; int widthCount = 0; @@ -783,7 +753,7 @@ ManagedString MicroBitImage::toString() parseIndex++; - if(widthCount == width-1) + if(widthCount == getWidth()-1) { parseBuffer[parseIndex] = '\n'; widthCount = 0; @@ -821,17 +791,17 @@ MicroBitImage MicroBitImage::crop(int startx, int starty, int cropWidth, int cro int newWidth = startx + cropWidth; int newHeight = starty + cropHeight; - if (newWidth >= width || newWidth <=0) - newWidth = width; + if (newWidth >= getWidth() || newWidth <=0) + newWidth = getWidth(); - if (newHeight >= height || newHeight <= 0) - newHeight = height; + if (newHeight >= getHeight() || newHeight <= 0) + newHeight = getHeight(); //allocate our storage. uint8_t cropped[newWidth * newHeight]; //calculate the pointer to where we want to begin cropping - uint8_t *copyPointer = bitmap + (width * starty) + startx; + uint8_t *copyPointer = getBitmap() + (getWidth() * starty) + startx; //get a reference to our storage uint8_t *pastePointer = cropped; @@ -841,9 +811,27 @@ MicroBitImage MicroBitImage::crop(int startx, int starty, int cropWidth, int cro { memcpy(pastePointer, copyPointer, newWidth); - copyPointer += width; + copyPointer += getWidth(); pastePointer += newHeight; } return MicroBitImage(newWidth, newHeight, cropped); } + +/** + * Check if image is read-only (i.e., residing in flash). + */ +bool MicroBitImage::isReadOnly() +{ + return ptr->isReadOnly(); +} + +/** + * Create a copy of the image bitmap. Used particularly, when isReadOnly() is true. + * + * @return an instance of MicroBitImage which can be modified independently of the current instance + */ +MicroBitImage MicroBitImage::clone() +{ + return MicroBitImage(getWidth(), getHeight(), getBitmap()); +} diff --git a/source/RefCounted.cpp b/source/RefCounted.cpp new file mode 100644 index 0000000..3d4105e --- /dev/null +++ b/source/RefCounted.cpp @@ -0,0 +1,46 @@ +#include "mbed.h" +#include "MicroBit.h" + +void RefCounted::init() +{ + // Initialize to one reference (lowest bit set to 1) + refCount = 3; +} + +static inline bool isReadOnlyInline(RefCounted *t) +{ + uint32_t refCount = t->refCount; + + if (refCount == 0xffff) + return true; // object in flash + + // Do some sanity checking while we're here + if (refCount == 1 || // object should have been deleted + (refCount & 1) == 0) // refCount doesn't look right + uBit.panic(MICROBIT_HEAP_ERROR); + + // Not read only + return false; +} + +bool RefCounted::isReadOnly() +{ + return isReadOnlyInline(this); +} + +void RefCounted::incr() +{ + if (!isReadOnlyInline(this)) + refCount += 2; +} + +void RefCounted::decr() +{ + if (isReadOnlyInline(this)) + return; + + refCount -= 2; + if (refCount == 1) { + free(this); + } +}