aboutsummaryrefslogtreecommitdiff
path: root/mayaPlug/shaveXPM.cpp
diff options
context:
space:
mode:
authorBen Marsh <[email protected]>2019-10-22 09:07:59 -0400
committerBen Marsh <[email protected]>2019-10-22 09:07:59 -0400
commitbd0027e737c6512397f841c22786274ed74b927f (patch)
treef7ffbdb8f3741bb7f24635616cc189cba5cb865c /mayaPlug/shaveXPM.cpp
downloadshave-and-a-haircut-bd0027e737c6512397f841c22786274ed74b927f.tar.xz
shave-and-a-haircut-bd0027e737c6512397f841c22786274ed74b927f.zip
Adding Shave-and-a-Haircut 9.6
Diffstat (limited to 'mayaPlug/shaveXPM.cpp')
-rw-r--r--mayaPlug/shaveXPM.cpp430
1 files changed, 430 insertions, 0 deletions
diff --git a/mayaPlug/shaveXPM.cpp b/mayaPlug/shaveXPM.cpp
new file mode 100644
index 0000000..b5ae555
--- /dev/null
+++ b/mayaPlug/shaveXPM.cpp
@@ -0,0 +1,430 @@
+// Shave and a Haircut
+// (c) 2019 Epic Games
+// US Patent 6720962
+
+#include <maya/MColor.h>
+#include <maya/MColorArray.h>
+#include <maya/MIntArray.h>
+#include <maya/MString.h>
+#include <maya/MStringArray.h>
+
+#include <math.h>
+#include <string.h>
+
+#include "shaveIO.h"
+#include "shaveXPM.h"
+
+shaveXPM::Node* shaveXPM::Node::head = NULL;
+unsigned long shaveXPM::Node::newMinError;
+unsigned long shaveXPM::Node::nextIndex = 0;
+unsigned long shaveXPM::Node::numLeafNodes = 0;
+
+
+shaveXPM::Node::Node(
+ unsigned char irmin, unsigned char igmin, unsigned char ibmin,
+ unsigned char irmax, unsigned char igmax, unsigned char ibmax
+)
+: numPixels(0)
+, numLeafPixels(0)
+, sumLeafR(0)
+, sumLeafG(0)
+, sumLeafB(0)
+, quantError(0)
+, rmax(irmax)
+, rmid((irmax + irmin)/2)
+, rmin(irmin)
+, gmax(igmax)
+, gmid((igmax + igmin)/2)
+, gmin(igmin)
+, bmax(ibmax)
+, bmid((ibmax + ibmin)/2)
+, bmin(ibmin)
+{
+ int i;
+
+ for (i = 0; i < 8; i++)
+ child[i] = NULL;
+}
+
+
+void shaveXPM::Node::init()
+{
+ numLeafNodes = 0;
+ head = new Node(0, 0, 0, 255, 255, 255);
+}
+
+
+void shaveXPM::Node::cleanup()
+{
+ if (head)
+ {
+ head->doCleanup();
+
+ delete head;
+ head = NULL;
+ }
+}
+
+
+void shaveXPM::Node::doCleanup()
+{
+ unsigned int i;
+
+ for (i = 0; i < 8; i++)
+ {
+ if (child[i])
+ {
+ child[i]->doCleanup();
+ delete child[i];
+ child[i] = NULL;
+ }
+ }
+}
+
+
+void shaveXPM::Node::addPixel(unsigned char r, unsigned char g, unsigned char b)
+{
+ head->doAddPixel(r, g, b, 0);
+}
+
+
+void shaveXPM::Node::doAddPixel(
+ unsigned char r, unsigned char g, unsigned char b, unsigned short level
+)
+{
+ numPixels++;
+
+ if (level == 7)
+ {
+ if (numLeafPixels == 0) numLeafNodes++;
+
+ numLeafPixels++;
+ sumLeafR += r;
+ sumLeafG += g;
+ sumLeafB += b;
+ }
+ else
+ {
+ //
+ // This node's colour space can be divided into 8 sub-spaces by
+ // cutting along the midpoint of R, G and B.
+ //
+ // The pixel should go into one of those 8 sub-spaces, so let's
+ // determine which one it falls into.
+ //
+ int i = 0;
+
+ if (r > rmid) i += 1;
+ if (g > gmid) i += 2;
+ if (b > bmid) i += 4;
+
+ //
+ // If we haven't yet created a node for that sub-space, do so now.
+ //
+ if (child[i] == NULL)
+ {
+ unsigned char crmin = rmin;
+ unsigned char crmax = rmid;
+ unsigned char cgmin = gmin;
+ unsigned char cgmax = gmid;
+ unsigned char cbmin = bmin;
+ unsigned char cbmax = bmid;
+
+ if (r > rmid)
+ {
+ crmin = rmid + 1;
+ crmax = rmax;
+ }
+
+ if (g > rmid)
+ {
+ cgmin = gmid + 1;
+ cgmax = gmax;
+ }
+
+ if (b > bmid)
+ {
+ cbmin = bmid + 1;
+ cbmax = bmax;
+ }
+
+ child[i] = new Node(crmin, cgmin, cbmin, crmax, cgmax, cbmax);
+ }
+
+ child[i]->doAddPixel(r, g, b, level+1);
+ }
+
+ //
+ // Add this pixel's quantizing error to the overall error for this
+ // node.
+ //
+ int rd = (int)r - (int)rmid;
+ int gd = (int)g - (int)gmid;
+ int bd = (int)b - (int)bmid;
+
+ quantError += (unsigned long)(rd*rd + gd*gd + bd*bd);
+}
+
+
+unsigned long shaveXPM::Node::prune(unsigned long minError)
+{
+ newMinError = 0;
+ head->doPrune(minError, false);
+
+ return newMinError;
+}
+
+
+bool shaveXPM::Node::doPrune(unsigned long minError, bool force)
+{
+ unsigned int i;
+
+ //
+ // If our quantizing error is within that being pruned, then force all
+ // of our children to be pruned as well, since it is possible that they
+ // have higher errors and would not otherwise be pruned.
+ //
+ if (quantError <= minError) force = true;
+
+ for (i = 0; i < 8; i++)
+ {
+ if (child[i] && child[i]->doPrune(minError, force))
+ {
+ //
+ // The child is being pruned, so add its pixels to our own and
+ // delete the child.
+ //
+ // Note that moving pixels into this node makes it a leaf node.
+ // If it was not already a leaf node, then we must increment
+ // the leaf node count.
+ //
+ if (numLeafPixels == 0) numLeafNodes++;
+
+ numLeafPixels += child[i]->numLeafPixels;
+ sumLeafR += child[i]->sumLeafR;
+ sumLeafG += child[i]->sumLeafG;
+ sumLeafB += child[i]->sumLeafB;
+
+ delete child[i];
+
+ child[i] = NULL;
+
+ //
+ // Decrement the leaf node count to take account of the child
+ // we just deleted.
+ //
+ numLeafNodes--;
+ }
+ }
+
+ if (force) return true;
+
+ if ((newMinError == 0) || (quantError < newMinError))
+ newMinError = quantError;
+
+ return false;
+}
+
+
+void shaveXPM::Node::getColours(struct Colour* colours)
+{
+ nextIndex = 0;
+ head->doGetColours(colours);
+}
+
+
+void shaveXPM::Node::doGetColours(struct Colour* colours)
+{
+ if (numLeafPixels > 0)
+ {
+ index = nextIndex++;
+
+ unsigned int round = numLeafPixels / 2;
+
+ colours[index].r = (unsigned char)((sumLeafR + round) / numLeafPixels);
+ colours[index].g = (unsigned char)((sumLeafG + round) / numLeafPixels);
+ colours[index].b = (unsigned char)((sumLeafB + round) / numLeafPixels);
+ }
+
+ unsigned int i;
+
+ for (i= 0; i < 8; i++)
+ if (child[i]) child[i]->doGetColours(colours);
+}
+
+
+unsigned long shaveXPM::Node::getIndex(
+ unsigned char r, unsigned char g, unsigned char b
+)
+{
+ return head->doGetIndex(r, g, b);
+}
+
+
+unsigned long shaveXPM::Node::doGetIndex(
+ unsigned char r, unsigned char g, unsigned char b
+)
+{
+ int i = 0;
+
+ if (r > rmid) i += 1;
+ if (g > gmid) i += 2;
+ if (b > bmid) i += 4;
+
+ if (child[i] == NULL) return index;
+
+ return child[i]->doGetIndex(r, g, b);
+}
+
+
+MString shaveXPM::toHex(unsigned char val)
+{
+ static char* hexChars = "0123456789abcdef";
+ static char hexStr[3];
+
+ hexStr[0] = hexChars[val >> 4];
+ hexStr[1] = hexChars[val & 0x0f];
+ hexStr[2] = '\0';
+
+ return MString(hexStr);
+}
+
+
+bool shaveXPM::write(
+ int width, int height, const unsigned char* rgba, const MString& filename
+)
+{
+ ofstream f(filename.asChar());
+
+ if (!f.good()) return false;
+
+ //
+ // Which chars can we use for colour indices in the XPM file?
+ //
+ const char* validChars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789"
+ "-_=+[]{}';:|/?.,>< `~!@#$%^&*()";
+ unsigned int numValidChars = (unsigned int)strlen(validChars);
+
+ //
+ // Sort the pixels in the image into an 8-way colour tree.
+ //
+ int numPixels = width * height;
+ int numPixelElements = numPixels * 4;
+ int i;
+
+ Node::init();
+
+ for (i = 0; i < numPixelElements; i += 4)
+ {
+ Node::addPixel(rgba[i], rgba[i+1], rgba[i+2]);
+ }
+
+ //
+ // The XPM funcs that Maya uses on Windows don't allow more than 2
+ // chars per colour index. So that marks the limit on the number of
+ // colours we can use.
+ //
+ unsigned long maxColours = numValidChars * numValidChars;
+ unsigned long minError = 0;
+
+ //
+ // Iteratively prune the colour tree until we're under our limit.
+ //
+ while (Node::numColours() > maxColours)
+ {
+ minError = Node::prune(minError);
+ }
+
+ unsigned int numColours = Node::numColours();
+
+ struct Colour* colours = new struct Colour[numColours];
+
+ Node::getColours(colours);
+
+ //
+ // Get colour indices for all of the pixels in the image.
+ //
+ unsigned int* indices = new unsigned int[numPixels];
+
+ for (i = 0; i < numPixelElements; i += 4)
+ {
+ indices[i >> 2] = Node::getIndex(rgba[i], rgba[i+1], rgba[i+2]);
+ }
+
+ Node::cleanup();
+
+ //
+ // Output XPM header info.
+ //
+ // Note that on Windows, Maya will reject an XPM file if the opening
+ // brace is not on the same line as the 'static char' declaration. I
+ // kid you not.
+ //
+ f << "/* XPM */" << endl;
+ f << "static char* swatch[] = {" << endl;
+
+ f << "/* width height ncolours charsPerPixel */" << endl;
+ f << "\"" << width << " " << height << " " << numColours << " 2\"," << endl;
+
+ //
+ // Generate and output the colour database. That is, the association
+ // between character sequences and the colours they will represent in
+ // the 'pixels' section.
+ //
+ MStringArray colourCodes;
+ char code[3];
+ f << "/* colours */" << endl;
+
+ for (i = 0; i < (int)numColours; i++)
+ {
+ unsigned int index = i;
+
+ code[0] = validChars[index / numValidChars];
+ code[1] = validChars[index % numValidChars];
+ code[2] = '\0';
+ colourCodes.append(code);
+
+#if defined(OSMac_) && (MAYA_API_VERSION >= 201600) && (MAYA_API_VERSION < 201700)
+ f << "\"" << code << " c #" << toHex(colours[i].r).asChar()
+ << toHex(colours[i].g).asChar() << toHex(colours[i].b).asChar()
+ << "\"," << endl;
+#else
+ f << "\"" << code << " c #" << toHex(colours[i].r)
+ << toHex(colours[i].g) << toHex(colours[i].b) << "\"," << endl;
+#endif
+ }
+
+ delete [] colours;
+
+ //
+ // Output the pixel data. Note that image data starts at the *bottom*
+ // of the image, but XPM format starts at the top. So we have to flip
+ // things around.
+ //
+ int col;
+
+ f << "/* pixels */" << endl;
+
+ for (i = numPixels - width; i >= 0; i -= width)
+ {
+ f << "\"";
+
+ for (col = 0; col < width; col++) {
+#if defined(OSMac_) && (MAYA_API_VERSION >= 201600) && (MAYA_API_VERSION < 201700)
+ f << colourCodes[indices[i+col]].asChar();
+#else
+ f << colourCodes[indices[i+col]];
+#endif
+ }
+
+ f << "\"," << endl;
+ }
+
+ f << "};" << endl;
+
+ f.close();
+
+ return true;
+}