Spec?
Hello,
I'm looking for a spec on the RleD/Rle8 resource - something explaining the format, so that I can write programs to interact with RleD/Rle8 files.
I've looked around for such a spec, but can't seem to find it.
Thanks.
A Google of the phrase "run length encoding" brought forth this specification which you might find informative. There were also many other Google hits for that phrase as well.
Arturo - RLE is a general concept (one I'm aquainted with), I'm looking for the specifics of the EVN implementation.
Graphics-specific RLEs often have interesting commands dealing with transparency, animation, and that sort of thing. Plus, both formats clearly encode information other than just pixels.
Thanks anyway.
I uploaded quite a bit of data on the format here for Orcaloverbri a while back, when we reverse-engineered the palette information for the rlë8 resource. I hope it helps, although it isn't terribly well organized. Thanks again to Kane O'Donnell, wherever he now is, for first sharing the info.
~ SP
Speaking of which, I posted the pallette info (critial for rle8 parsers) from that conversation not too long ago. It may well be the last topic I made, so I don't think it's be hard to find. I'll dig it up later.
Thanks for all the information!
I'm rather disapointed that this had to be determined through reverse engineering - after all, one of EV's main claims to fame is it's open, extensable nature. (The latter and former go hand in hand, usually.)
(edit: oh, I see, it is SpriteWorld's format. Could have been better documented...)
This post has been edited by Bryce : 28 March 2005 - 07:25 PM
Okay, here's my implementation. This is free for anyone to use, in the spirit of open-source.
Unless you want your application to depend on SDL, of course, you'll have to change it to use a different method of drawing. Just about anything that will present the pixels of a surface/window as a pointer to the raw data (not a two-dimensional array) ought to be able to be adapted for use in place of SDL in this code. Also, you will likely want to change it to use resources (rather than files containing the data of the resource) to adapt the code to a Mac enviroment. More on that below.
Even if you don't use it directly, you can still see how it works... sometimes SpriteWorld's spec is a little unclear. ("Are we counting bytes or pixels?" Was a big one for some of the commands. With rleD, this is an important question.)
Header file:
/***************************************************************************
* RLErsrc.h
*
* Tue Mar 29 14:31:55 2005
* Copyright 2005 Bryce Schroeder
* bryce@lanset.com
*
* This file defines the namespace RLE, which contains various things for
* dealing with EV Nova's rle8 and rleD image resources.
* These resources are 8 and 16 bit run-length encoded sprite images.
* The code assumes that the resource has been dumped into a file.
* It translates them into the 32-bit SDL surfaces.
****************************************************************************/
#ifndef _RLERSRC_H
#define _RLERSRC_H
#include <SDL/SDL.h> // Depends on SDL for Uint* data types, Surface, and color
// mapping features.
#include <fstream> // Uses fstream to read from the files.
#include "resourceutil.h"
//#include "free-elite.h"
// These macros may seem endian-dependant, they are,
// BUT the system as a whole is not because all incoming
// data is converted to host byte order. (Whichever that is.)
#define RLEb0(X) ((Uint8)(((X)&0xFF000000)/0x1000000))
#define RLECOUNT(X) ((Uint32)(((X)&0x00FFFFFF)))
#define RLEb1(X) ((Uint8)(((X)&0x00FF0000)/0x10000))
#define RLEb2(X) ((Uint8)(((X)&0x0000FF00)/0x100))
#define RLEb3(X) ((Uint8)((X)&0x000000FF))
namespace RLE {
const Uint16 RLE8 = 8;
const Uint16 RLED = 16;
// The names of these constants follow spriteworld conventions.
const Uint8 kEndShapeToken = 0,
kLineStartToken = 1,
kSkipPixelsToken = 3,
kDrawPixelsToken = 2,
kSingleColorToken = 4;
/* This class, RLE::Sprite, is a rleX file. It loads and translates the
* file, leaving the user with a set of SDL_Surfaces, one for each frame.
* It does not free the surfaces when destructed -
* the program is assumed to be using them. */
class Sprite {
public:
Uint32 width, height, nshapes, depth, paletteID;
// Width of sprite, height, and number of shapes.
SDL_Surface **shapes;
// Array of SDL_Surfaces to hold the shapes in the sprite.
char *error;
// Store a description of an error. NULL if no error.
Sprite(std::ifstream &file);
private:
SDL_Surface *loadShape(std::ifstream &file);
Uint32 *loadLine(std::ifstream &file,
Uint32 *current,
SDL_Surface *frame
);
Uint32 *loadLine16(std::ifstream &file,
Uint32 *current,
SDL_Surface *frame
);
};
#ifdef RLERSRC_C // We only include the nova palette in the one file that
// needs it, not in any old module that uses RLE::Sprite.
#include "novapalette.h"
#endif
}
#endif /* _RLERSRC_H */
Implementation file:
/***************************************************************************
* RLErsrc.cc
*
* Tue Mar 29 15:17:01 2005
* Copyright 2005 Bryce Schroeder
* bryce@lanset.com
* Contains code for RLErsrc.h. See documentation there.
****************************************************************************/
#define RLERSRC_C
#include "RLErsrc.h"
#include "sdlutil.h"
#include <iostream>
/* This constructor loads a RLE from a file. */
RLE::Sprite::Sprite(std::ifstream &file) {
// clear the error field.
error = NULL;
// Read the header.
width = read16u(file);
height = read16u(file);
depth = read16u(file);
paletteID = read16u(file);
nshapes = read16u(file);
// Skip the reserved
read16u(file); read32u(file);
// Allocate memory to store all the shapes.
shapes = new (SDL_Surface*) (nshapes);
for (unsigned int cframe =0; cframe < nshapes; cframe++) {
shapes(cframe) = loadShape(file);
if (!shapes(cframe))
std::cerr << error << std::endl; std::cerr.flush();
}
}
/* Loads an individual shape from an rle file.
* The data stream is assumed to be advanced to the begining of the
* shape to be read. */
SDL_Surface *RLE::Sprite::loadShape (std::ifstream &file) {
SDL_Surface *frame = SDL_CreateRGBSurface(
SDL_SWSURFACE|SDL_SRCALPHA,
width, height, 32, RMASK, GMASK, BMASK, AMASK
);
SDL_LockSurface(frame);
Uint32 *current = (Uint32*)frame->pixels;
if (depth == RLE8) {
for (unsigned int y = 0; y < height; y++) {
current = loadLine(file, current, frame);
if (!current) {
error = "RLE8 decoding error";
return NULL;
}
}
} else {
for (unsigned int y = 0; y < height; y++) {
current = loadLine16(file, current, frame);
if (!current) {
error = "RLED decoding error";
return NULL;
}
}
}
if (RLEb0(read32u(file)) != kEndShapeToken) {
error = "Unterminated shape";
return NULL;
}
SDL_UnlockSurface(frame);
return frame;
}
/* Parses a single line's worth of Run-length encoded data. */
Uint32 *RLE::Sprite::loadLine(
std::ifstream &file,
Uint32 *current,
SDL_Surface *frame
) {
// We do some preliminaries. Make a transparent pixel for filling.
Uint32 transparent = SDL_MapRGBA(frame->format, 0,0,0,0);
Uint32 fillcolor; Uint8 index;
const Uint32 masks() = {0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF};
const Uint32 shifts() = {0x1000000,0x10000,0x100,0x1};
// This pointer will later be used to make sure we finish the line with
// transparent pixels.
Uint32 *end = current + width;
// First step in parsing a line: make sure the line start token is here.
Uint32 begin = read32u(file);
if (RLEb0(begin) != kLineStartToken) return NULL;
Uint32 command, word = 0; // Used for all data input.
// Initalize our byte count.
int bytes = RLECOUNT(begin);
// Loop, doing RLE commands.
while (bytes > 0) {
command = read32u(file);
bytes -= 4; // Throughout the function, we must keep rigorous byte count
switch (RLEb0(command)) {
case kSkipPixelsToken:
/* This token is for skiping ahead, for transparency.
Rle8/D as found in EVN does not support multiple levels of
transparency, although by using a different palette,
other levels could be used with Rle8. Similarly, the unused
bit in rleD could allow one level of semi-transparency, with
a modifed decoder. Both alterations would be fully
compatable with standard rle8/D, but would not of course
make the transparency feature avalible to older parsers.
EVN implements transparency levels on the shan level.
*/
for (unsigned int i = 0; i < RLECOUNT(command); i++)
*(current++) = transparent;
break;
case kDrawPixelsToken:
// Draws pixels, converting indexed color to RGBA.
for (unsigned int i = 0; i < RLECOUNT(command); i++) {
if (!(i%4)) {
word = read32u(file); bytes -= 4;
}
index = (word&masks((i%4)))/shifts((i%4));
*(current++) = SDL_MapRGBA(frame->format,
RLE::NovaPalette(index).r,
RLE::NovaPalette(index).g,
RLE::NovaPalette(index).b,
0xFF
);
}
break;
case kSingleColorToken:
word = read32u(file);
bytes -= 4;
fillcolor = SDL_MapRGBA(
frame->format,
RLE::NovaPalette(RLEb3(word)).r,
RLE::NovaPalette(RLEb3(word)).g,
RLE::NovaPalette(RLEb3(word)).b,
0xFF
);
for (unsigned int i = 0; i < RLECOUNT(command); i++)
*(current++) = fillcolor;
break;
}
} // main loop
// Now we finish up edge pixels.
while (current < end)
*(current++) = transparent;
return current;
}
/* Parses a single line's worth of Run-length encoded 16-bit data. */
Uint32 *RLE::Sprite::loadLine16(
std::ifstream &file,
Uint32 *current,
SDL_Surface *frame
) {
// We do some preliminaries. Make a transparent pixel for filling.
Uint32 transparent = SDL_MapRGBA(frame->format, 0,0,0,0);
Uint16 color;
// This pointer will later be used to make sure we finish the line with
// transparent pixels.
Uint32 *end = current + width;
// First step in parsing a line: make sure the line start token is here.
Uint32 begin = read32u(file);
if (RLEb0(begin) != kLineStartToken) return NULL;
Uint32 command, word = 0; // Used for all data input.
// Initalize our byte count.
int bytes = RLECOUNT(begin);
// Loop, doing RLE commands.
while (bytes > 0) {
command = read32u(file);
bytes -= 4; // Throughout the function, we must keep rigorous byte count
switch (RLEb0(command)) {
case kSkipPixelsToken:
for (unsigned int i = 0; i < RLECOUNT(command)/2; i++)
*(current++) = transparent;
break;
case kDrawPixelsToken:
for (unsigned int i = 0; i < RLECOUNT(command); i+=4) {
word = read32u(file); bytes -= 4;
// Bitwise math is fun. Just repeat that over and over...
/* The pixel format used by rleD is that which is used on
* the Macintosh. A 16-bit pixel is stored as :
* (MSB) XRRR RRGG GGGB BBBB (LSB). Or, big-endian 555.
* X, the most significant bit, is ignored.
* Two pixels are packed into a word, the first pixel is the
* first byte.
*/
color = (word&0xFFFF0000)/0x10000;
*(current++) = SDL_MapRGBA(frame->format,
((color&0x7C00)>>7),
((color&0x03E0)>>2),
((color&0x001F)<<3),
0xFF
);
if (i+2 < RLECOUNT(command)) {
color = (word&0x0000FFFF);
*(current++) = SDL_MapRGBA(frame->format,
((color&0x7C00)>>7),
((color&0x03E0)>>2),
((color&0x001F)<<3),
0xFF
);
}
}
break;
case kSingleColorToken:
word = read32u(file);
bytes -= 4;
color = SDL_MapRGBA(
frame->format,
100,100,100,
0xFF
);
for (unsigned int i = 0; i < RLECOUNT(command); i++)
*(current++) = color;
break;
}
} // main loop
// Now we finish up edge pixels.
while (current < end)
*(current++) = transparent;
return current;
}
This file is resourceutil. It is currently rather bare, but it has the functions to support rle8/D resources dumped as files. The whole code will of course have to be adapted to use resources in a fork, the way to do that is to change RLE::Sprite's constructor to take a pointer to the input data, and replace the file-reading read*u() functions with ones that return data and bump the pointer forward.
Header:
/***************************************************************************
* resourceutil.h
*
* Tue Mar 29 15:02:15 2005
* Copyright 2005 Bryce Schroeder
* bryce@lanset.com
* Contains functions for dealing with the data contained in file-dumped
* Mac resources. (Such as reading big-endian integers of various sizes.)
****************************************************************************/
#ifndef _RESOURCEUTIL_H
#define _RESOURCEUTIL_H
#include <fstream>
#include <SDL/SDL.h>
/* These functions are utilitys for reading big-endian integers from
an istream. */
Uint32 read32u(std::ifstream &file);
Uint32 read24u(std::ifstream &file);
Uint16 read16u(std::ifstream &file);
// This one is a macro.
#define read8u(X) ((Uint8)((X).get()))
#endif /* _RESOURCEUTIL_H */
Implementation file:
/***************************************************************************
* resourceutil.cc
*
* Tue Mar 29 15:22:46 2005
* Copyright 2005 Bryce Schroeder
* bryce@lanset.com
****************************************************************************/
#include "resourceutil.h"
Uint32 read32u(std::ifstream &file) {
Uint8 bytes(4);
// Yes, a loop could be used, but I can't imagine it being more clear
// or fast.
bytes(0) = file.get(); bytes(1) = file.get();
bytes(2) = file.get(); bytes(3) = file.get();
return bytes(0)*0x1000000 + bytes(1)*0x10000 + bytes(2)*0x100 + bytes(3);
}
Uint32 read24u(std::ifstream &file) {
Uint8 bytes(3);
bytes(0) = file.get(); bytes(1) = file.get();
bytes(2) = file.get();
return bytes(0)*0x10000 + bytes(1)*0x100 + bytes(2);
}
Uint16 read16u(std::ifstream &file) {
Uint8 b1, b2;
b1 = file.get(); b2 = file.get();
return b1*0x100 + b2;
}
sdlutil.h just contains definitions of AMASK, RMASK, GMASK, and BMASK, for your system's byte sex.
For big endian, (which the PowerPC in a Mac is)
#define RMASK 0xff000000
#define GMASK 0x00ff0000
#define BMASK 0x0000ff00
#define AMASK 0x000000ff
For little endian systems (Intel and AMD CPUs) , the order is reversed (AMASK is 0xFF000000, etc.)
novapalette.h, unsuprisingly, contains the EV nova palette, from orcaloverbri9 - converted into a C++ friendly format by a simple python script. (So simple that I just made it in the python interpreter, use sed, perl, or whatever you perfer to convert it.)
It looks like this:
SDL_Color NovaPalette(256) = {
{0xFF, 0xFF, 0xFF, 0x00},
{0xFF, 0xFF, 0xCC, 0x00},
{0xFF, 0xFF, 0x99, 0x00},
{0xFF, 0xFF, 0x66, 0x00},
{0xFF, 0xFF, 0x33, 0x00},
{0xFF, 0xFF, 0x00, 0x00},
{0xFF, 0xCC, 0xFF, 0x00},
....
{0x11, 0x11, 0x11, 0x00},
{0x00, 0x00, 0x00, 0x00}
};
If you actually want to compile any of this, you will of course need a host program with a main function.
Just send me an email and I'll send a copy of a makefile and driver program.
The Polaris are invading Linux! Oh no!
Thanks!
(edit: Removed some information from the comments that I don't want to share with the world at this point. It shouldn't be missed, the comments relevent to RLE files are retained.)
(edit: Macs use the big-endian byte sex.)
This post has been edited by Bryce : 31 March 2005 - 07:31 PM
Sorry to dig this topic, but know that, while I was off the 'net for spring break, I did some programming stuff however based on this. I need to tweak it some more (say, adding the Rlë8 palette I forgot to take before not being able to go to the 'net anymore for two weeks, and allowing to open a file other than named "plug-in" at the root level of a hard drive named "Macintosh HD", ah the joys of hardcoded paths...) this weekend before I can show it to you, however. It won't be really more useful than Mission Computer, EVONE or NovaTools, but hey, I did it myself, and it reads from resources contrary to Bryce's stuff.
Okay, as I promised, here's what I did. This was spawned by the fact I wanted to enjoy the pure beauty of the special, put-in-their-own-optional-file-so-much-they're-big, rlëD's of rEV, which was released like one month ago. But NovaTools didn't display all of these huge sprites, so I couldn't properly enjoy them. But I thought I could write my own app to display them, since I had seen this topic before (yeah, I know, I could have used MC or EVONE, but when you're in a programming mood, you take any excuse to code). And that's what I did.
The three source files are in the codeboxes below (note to bryce: you may wish to use ( codeboxes ) next time, since they can scroll), and I added the zipped built program as an attachment to this post (it should work, please tell me if it doesn't). Notice it doesn't (yet?) support dragging and dropping: you need to launch the program, then it will prompt you for the file to open.
Notice that, in order to rebuild the app, you will need the main.nib pseudo-file (in fact, a folder) found in the built app. Also, this code and app will of course only work on the Mac (since it uses resource and the Resource Manager), and I've not been able to learn enough QuickDraw to get rid of Quartz, so right now it can't be compiled nor run on OS9 (whatever, this is something I couldn't test anyway, since my development tools don't allow me to target OS9). It might work on OSX 10.1, I only have 10.2.
Bryce, there is a bug in your code, that I fixed - it originally caused my program to crash, due to the singlecolor token being used in rEV rlëDs, while it seems they're not used in stock Nova scenario rlës, thus why you may have missed it. It's at the end of RLErsrc.c
Enjoy!
EDIT: removed old build. Please download the latest.
/***************************************************************************
* RLErsrc.h
*
* Tue Mar 29 14:31:55 2005
* Copyright 2005 Bryce Schroeder
* bryce@lanset.com
*
* This file defines the namespace RLE, which contains various things for
* dealing with EV Nova's rle8 and rleD image resources.
* These resources are 8 and 16 bit run-length encoded sprite images.
* The code assumes that the resource has been dumped into a file.
* It translates them into the 32-bit SDL surfaces.
****************************************************************************/
/* Modified by Zacha Pedro: Mac-specific with resources and stuff, no more SDL, plain C, etc...*/
#ifndef _RLERSRC_H
#define _RLERSRC_H
/* I'm gonna tell you something. I don't really like C++. I know enough
to read it, but I can't program in C++ (even when using only normal
C features), due to some stuff such as if () having to take bool, but
primarily because I end up no longer knowing what I'm doing when using
C++ specific stuff. Therefore, I rewrote everything in C.*/
#include <Carbon/Carbon.h>
#define RLEb0(X) ((UInt8)(((X)>>24)&0xFF))
#define RLECOUNT(X) ((UInt32)(((X)&0x00FFFFFF)))
#define RLEb1(X) ((UInt8)(((X)>>16)&0xFF))
#define RLEb2(X) ((UInt8)(((X)>>8)&0xFF))
#define RLEb3(X) ((UInt8)((X)&0xFF))
#define RLE8 8
#define RLED 16
enum {
kEndShapeToken = 0,
kLineStartToken = 1,
kSkipPixelsToken = 3,
kDrawPixelsToken = 2,
kSingleColorToken = 4
};
/* I extended Sprite to store all the info I needed to manage the
decoded data and the window displaying it.*/
struct Sprite {
UInt32 width, height, nshapes, depth, paletteID, currentlyDisplayedShape;
/* last UInt32 for the timer.*/
UInt32 **shapes;
EventLoopTimerRef timerToRemove; // the installed timer to rotate the frames.
Boolean running; // whether it is currently running or not.
WindowRef window; // the window on which the Sprite is displayed
Handle resHandle; /* the handle to the resource to decode; only
valid within the context of sprite creation!*/
};
typedef struct Sprite* SpritePtr;
#endif /* _RLERSRC_H */
/***************************************************************************
* RLErsrc.cc
*
* Tue Mar 29 15:17:01 2005
* Copyright 2005 Bryce Schroeder
* bryce@lanset.com
* Contains code for RLErsrc.h. See documentation there.
****************************************************************************/
/*
* RLErsrc.c
* ViewRLE
*
* Created by Bryce on some date.
* Modified by Zacha Pedro on some later date.
*
*/
#include "RLErsrc.h"
/* Normally, this file shouldn't need to call stuff from main, but invoking
the window require the size of the sprite to be known, while I need to invoke
it before actual decoding happens to show the frames as they decoded. Therefore,
I declare InvoquerFenetre here to call it.*/
extern void InvoquerFenetre(SpritePtr spr);
/* Now that's data! I should probably store that in a better way (like, a good 'ol
resource, an xml file, a raw file...) for better modification (without requiring
recompilation), but I'm not feeling like it right now.*/
static const UInt32 novaPalette(256) = {
0xFFFFFF, 0xFFFFCC, 0xFFFF99, 0xFFFF66, 0xFFFF33, 0xFFFF00,
0xFFCCFF, 0xFFCCCC, 0xFFCC99, 0xFFCC66, 0xFFCC33, 0xFFCC00,
0xFF99FF, 0xFF99CC, 0xFF9999, 0xFF9966, 0xFF9933, 0xFF9900,
0xFF66FF, 0xFF66CC, 0xFF6699, 0xFF6666, 0xFF6633, 0xFF6600,
0xFF33FF, 0xFF33CC, 0xFF3399, 0xFF3366, 0xFF3333, 0xFF3300,
0xFF00FF, 0xFF00CC, 0xFF0099, 0xFF0066, 0xFF0033, 0xFF0000,
0xCCFFFF, 0xCCFFCC, 0xCCFF99, 0xCCFF66, 0xCCFF33, 0xCCFF00,
0xCCCCFF, 0xCCCCCC, 0xCCCC99, 0xCCCC66, 0xCCCC33, 0xCCCC00,
0xCC99FF, 0xCC99CC, 0xCC9999, 0xCC9966, 0xCC9933, 0xCC9900,
0xCC66FF, 0xCC66CC, 0xCC6699, 0xCC6666, 0xCC6633, 0xCC6600,
0xCC33FF, 0xCC33CC, 0xCC3399, 0xCC3366, 0xCC3333, 0xCC3300,
0xCC00FF, 0xCC00CC, 0xCC0099, 0xCC0066, 0xCC0033, 0xCC0000,
0x99FFFF, 0x99FFCC, 0x99FF99, 0x99FF66, 0x99FF33, 0x99FF00,
0x99CCFF, 0x99CCCC, 0x99CC99, 0x99CC66, 0x99CC33, 0x99CC00,
0x9999FF, 0x9999CC, 0x999999, 0x999966, 0x999933, 0x999900,
0x9966FF, 0x9966CC, 0x996699, 0x996666, 0x996633, 0x996600,
0x9933FF, 0x9933CC, 0x993399, 0x993366, 0x993333, 0x993300,
0x9900FF, 0x9900CC, 0x990099, 0x990066, 0x990033, 0x990000,
0x66FFFF, 0x66FFCC, 0x66FF99, 0x66FF66, 0x66FF33, 0x66FF00,
0x66CCFF, 0x66CCCC, 0x66CC99, 0x66CC66, 0x66CC33, 0x66CC00,
0x6699FF, 0x6699CC, 0x669999, 0x669966, 0x669933, 0x669900,
0x6666FF, 0x6666CC, 0x666699, 0x666666, 0x666633, 0x666600,
0x6633FF, 0x6633CC, 0x663399, 0x663366, 0x663333, 0x663300,
0x6600FF, 0x6600CC, 0x660099, 0x660066, 0x660033, 0x660000,
0x33FFFF, 0x33FFCC, 0x33FF99, 0x33FF66, 0x33FF33, 0x33FF00,
0x33CCFF, 0x33CCCC, 0x33CC99, 0x33CC66, 0x33CC33, 0x33CC00,
0x3399FF, 0x3399CC, 0x339999, 0x339966, 0x339933, 0x339900,
0x3366FF, 0x3366CC, 0x336699, 0x336666, 0x336633, 0x336600,
0x3333FF, 0x3333CC, 0x333399, 0x333366, 0x333333, 0x333300,
0x3300FF, 0x3300CC, 0x330099, 0x330066, 0x330033, 0x330000,
0x00FFFF, 0x00FFCC, 0x00FF99, 0x00FF66, 0x00FF33, 0x00FF00,
0x00CCFF, 0x00CCCC, 0x00CC99, 0x00CC66, 0x00CC33, 0x00CC00,
0x0099FF, 0x0099CC, 0x009999, 0x009966, 0x009933, 0x009900,
0x0066FF, 0x0066CC, 0x006699, 0x006666, 0x006633, 0x006600,
0x0033FF, 0x0033CC, 0x003399, 0x003366, 0x003333, 0x003300,
0x0000FF, 0x0000CC, 0x000099, 0x000066, 0x000033,
0xEE0000, 0xDD0000, 0xBB0000, 0xAA0000, 0x880000, 0x770000, 0x550000, 0x440000, 0x220000, 0x110000,
0x00EE00, 0x00DD00, 0x00BB00, 0x00AA00, 0x008800, 0x007700, 0x005500, 0x004400, 0x002200, 0x001100,
0x0000EE, 0x0000DD, 0x0000BB, 0x0000AA, 0x000088, 0x000077, 0x000055, 0x000044, 0x000022, 0x000011,
0xEEEEEE, 0xDDDDDD, 0xBBBBBB, 0xAAAAAA, 0x888888, 0x777777, 0x555555, 0x444444, 0x222222, 0x111111, 0x000000,
};
static const float CGBlack(4)={0,0,0,1};
UInt32* LoadShape(Handle hand, unsigned int* offsetPtr, UInt16 width, UInt16 height, UInt16 depth);
UInt32* LoadLine(Handle hand,
unsigned int* offsetPtr,
UInt32* current,
UInt16 width, UInt16 height);
UInt32* LoadLine16(Handle hand,
unsigned int* offsetPtr,
UInt32* current,
UInt16 width, UInt16 height);
static void ReleaseData(void *info, const void *data, size_t size)
{
/* ReleaseData will be passed to CGDataProviderCreateWithData,
and is assumed to free the memory itself when CGDataProvider is
relased, but it is to do ABSOLUTELY NOTHING. Indeed, I need to
release the CGDataProvider since I'm not allowed to change the
memory it references while it exists, but I need to keep the
memory buffer!*/
}
/* hand is the handle to the rlëX resource to decode. What's a handle?
It's a pointer to a pointer to data. It's to support relocatable memory
blocks: the handle points to some part of a non-relocatable block called
the master pointer. The master pointer itself points to the allocated memory.
When the memory manager needs to compact the heap to make room for an
allocation, it moves relocatable memory blocks, and updates the start
adresses of the moved blocks in the master pointer. That way, by using
the value pointed by hand, we are sure we indeed point to the current
start adress of the memory block.
As a consequence, browsing the resource is not as simple as browsing a file
or incrementing a pointer in a buffer. I keep the handle and perpetually
dereference it, and add to the address thus gotten an offset, that I use
and pass by adress so that the position is updated fo everyone.*/
/* ControlRef?! I pass the reference to the scroll bar in the main window,
so that it can get updated while the shapes are decoded. It does
not actually work, probably because the control is actually drawn at event
retrieval time.*/
SpritePtr SpriteCreate(Handle hand, ControlRef controle)
{
SpritePtr result;
unsigned int cframe;
unsigned int offset=16;
CGDataProviderRef dataObject;
CGImageRef imageObject;
CGContextRef curContext;
result=malloc(sizeof(struct Sprite));
// Read the header.
result->width = ((UInt16*)*hand)(0);
result->height = ((UInt16*)*hand)(1);
result->depth = ((UInt16*)*hand)(2);
result->paletteID = ((UInt16*)*hand)(3);
result->nshapes = ((UInt16*)*hand)(4);
result->currentlyDisplayedShape=0;
SetControl32BitMaximum(controle, result->nshapes);
// Allocate memory to store all the shapes.
result->shapes = calloc(result->nshapes,sizeof(void*));
result->resHandle=hand;
InvoquerFenetre(result);
for (cframe =0; cframe < result->nshapes; cframe++)
{
result->shapes(cframe) = LoadShape(hand, &offset, result->width, result->height, result->depth);
/* Each time a shape is decoded, I advance the progress bar.*/
SetControl32BitValue(controle,cframe);
Draw1Control(controle);
if (result->shapes(cframe)==NULL)
{
result->nshapes=cframe;
break;
}
/* I show the shapes as they are decoded.*/
QDBeginCGContext(GetWindowPort(result->window),&curContext);
dataObject=CGDataProviderCreateWithData (NULL,
result->shapes(cframe),
result->width*result->height*4,
&ReleaseData);
imageObject= CGImageCreate(result->width,result->height,8,32,result->width*4,
CGColorSpaceCreateDeviceRGB(),
kCGImageAlphaLast,
dataObject, NULL,0,
kCGRenderingIntentDefault);
CGContextSetFillColorSpace(curContext, CGColorSpaceCreateDeviceRGB());
CGContextSetFillColor(curContext,CGBlack);
CGContextFillRect(curContext,
CGRectMake(0.0,0.0,result->width,result->height));
CGContextDrawImage(curContext,
CGRectMake(0.0,0.0,result->width,result->height),
imageObject);
CGImageRelease(imageObject);
CGDataProviderRelease(dataObject);
CGContextFlush(curContext);
QDEndCGContext(GetWindowPort(result->window),&curContext);
}
return result;
}
/* Instead of the file, I pass the handle and a pointer to the current
offset, so that it can get updated is a file would.*/
UInt32* LoadShape(Handle hand, unsigned int* offsetPtr,
UInt16 width, UInt16 height, UInt16 depth)
{
UInt32 *result, *current;
unsigned int i;
result=malloc(width*height*4);
current=result;
if (depth == RLE8)
{
for (i = 0; i < height; i++)
{
current = LoadLine(hand, offsetPtr, current, width, height);
if (!current)
{
printf("RLE8 decoding error\n");
return NULL;
}
}
}
else
{
for (i = 0; i < height; i++)
{
current = LoadLine16(hand, offsetPtr, current, width, height);
if (!current)
{
printf("RLED decoding error\n");
return NULL;
}
}
}
if (RLEb0(*(UInt32*)(*hand+*offsetPtr)) != kEndShapeToken)
{
printf("Unterminated shape\n");
*offsetPtr+=4;
return NULL;
}
*offsetPtr+=4;
return result;
}
/* Parses a single line's worth of Run-length encoded data. */
UInt32* LoadLine(Handle hand,
unsigned int* offsetPtr,
UInt32* current,
UInt16 width, UInt16 height)
{
// We do some preliminaries. Make a transparent pixel for filling.
UInt32 transparent = 0; // For Quartz, that is...
UInt32 fillcolor;
UInt8 index;
/*const Uint32 masks() = {0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF};
const Uint32 shifts() = {0x1000000,0x10000,0x100,0x1};*/
// This pointer will later be used to make sure we finish the line with
// transparent pixels.
UInt32 *end = current + width;
// First step in parsing a line: make sure the line start token is here.
UInt32 begin;
/* Here's how I read from the resource: I dereference both the handle and
the pointer to the offset in it, add them, and get the value. Notice
that, is Handle is actually char**, I indeed add *offsetPtr bytes,
and only then I cast as a pointer to something else, to avoid bad
surprises with undesired pointer arithmetic*/
begin=*(UInt32*)(*hand+*offsetPtr);
// Then I update the offset depending on the size of the read data.*/
*offsetPtr+=4;
if (RLEb0(begin) != kLineStartToken) return NULL;
UInt32 command, word = 0; // Used for all data input.
// Initalize our byte count.
unsigned int i;
int bytes = RLECOUNT(begin);
// Loop, doing RLE commands.
while (bytes > 0)
{
command = *(UInt32*)(*hand+*offsetPtr);
*offsetPtr+=4;
bytes -= 4; // Throughout the function, we must keep rigorous byte count
switch (RLEb0(command))
{
case kSkipPixelsToken:
/* This token is for skiping ahead, for transparency.
Rle8/D as found in EVN does not support multiple levels of
transparency, although by using a different palette,
other levels could be used with Rle8. Similarly, the unused
bit in rleD could allow one level of semi-transparency, with
a modifed decoder. Both alterations would be fully
compatable with standard rle8/D, but would not of course
make the transparency feature avalible to older parsers.
EVN implements transparency levels on the shan level.
*/
for (i = 0; i < RLECOUNT(command); i++)
*(current++) = transparent;
break;
case kDrawPixelsToken:
// Draws pixels, converting indexed color to RGBA.
for (i = 0; i < RLECOUNT(command); i++)
{
if (!(i%4))
{
word = *(UInt32*)(*hand+*offsetPtr);
*offsetPtr+=4;
bytes -= 4;
}
/* Quite frankly... I don't like your "byte picking" method Bryce.
It's best to shift first, then mask, with always the same mask.*/
index = (word>>8*(3-(i%4)))&0xFF;
*(current++) = ((novaPalette(index))<<8) + 0xFF;
}
break;
case kSingleColorToken:
word = *(UInt32*)(*hand+*offsetPtr);
*offsetPtr+=4;
bytes -= 4;
fillcolor = ((novaPalette(RLEb3(word)))<<8) + 0xFF;
for (i = 0; i < RLECOUNT(command); i++)
{
*(current++) = fillcolor;
}
break;
}
} // main loop
// Now we finish up edge pixels.
while (current < end)
{
*(current++) = transparent;
}
return current;
}
/* Parses a single line's worth of Run-length encoded 16-bit data. */
UInt32* LoadLine16(Handle hand,
unsigned int* offsetPtr,
UInt32* current,
UInt16 width, UInt16 height)
{
// We do some preliminaries. Make a transparent pixel for filling.
UInt32 transparent = 0;
UInt16 color;
// This pointer will later be used to make sure we finish the line with
// transparent pixels.
UInt32 *end = current + width;
// First step in parsing a line: make sure the line start token is here.
UInt32 begin;
begin=*(UInt32*)(*hand+*offsetPtr);
*offsetPtr+=4;
if (RLEb0(begin) != kLineStartToken) return NULL;
UInt32 command, word = 0; // Used for all data input.
// Initalize our byte count.
int bytes = RLECOUNT(begin);
unsigned int i;
// Loop, doing RLE commands.
while (bytes > 0)
{
command = *(UInt32*)(*hand+*offsetPtr);
*offsetPtr+=4;
bytes -= 4; // Throughout the function, we must keep rigorous byte count
switch (RLEb0(command))
{
case kSkipPixelsToken:
for (i = 0; i < RLECOUNT(command)/2; i++)
{
*(current++) = transparent;
}
break;
case kDrawPixelsToken:
for (i = 0; i < RLECOUNT(command); i+=4)
{
word = *(UInt32*)(*hand+*offsetPtr);
*offsetPtr+=4;
bytes -= 4;
// Bitwise math is fun. Just repeat that over and over...
/* The pixel format used by rleD is that which is used on
* the Macintosh. A 16-bit pixel is stored as :
* (MSB) XRRR RRGG GGGB BBBB (LSB). Or, big-endian 555.
* X, the most significant bit, is ignored.
* Two pixels are packed into a word, the first pixel is the
* first byte.
*/
/* Yet again, isn't it simpler that way?*/
color = word>>16;
*(current++) = (((color>>7)&0xF8)<<24 |
((color>>2)&0xF8)<<16 |
((color<<3)&0xF8)<<8 |
0xFF);
if (i+2 < RLECOUNT(command))
{
color = word;
*(current++) = (((color>>7)&0xF8)<<24 |
((color>>2)&0xF8)<<16 |
((color<<3)&0xF8)<<8 |
0xFF);
}
}
break;
case kSingleColorToken:
word = *(UInt32*)(*hand+*offsetPtr);
*offsetPtr+=4;
bytes -= 4;
/* Err... excuse me, sir, but isn't the ACTUAL color to span contained
(somehow) in word? Can't check the docs right now.*/
color = word;
/* I overwrite word (the old value won't be needed anyway) so that I
I don't recompute the RGBA color for each pixel in the loop.*/
word = (((color>>7)&0xF8)<<24 |
((color>>2)&0xF8)<<16 |
((color<<3)&0xF8)<<8 |
0xFF);
/* The biggest (and so far, the only) mistake I found in your code,
Bryce: here you must skip HALF the count in pixels, just as in the case
of pixel skipping (in a way, it's a bit like this command replaces
RLECOUNT(command) bytes of RLE data, thus the need to skip half this
amount in actual pixels, in case of rlëD).*/
for (i = 0; i < RLECOUNT(command)/2; i++)
{
*(current++) = word;
}
break;
}
} // main loop
// Now we finish up edge pixels.
while (current < end)
*(current++) = transparent;
return current;
}
/* With me not using SDL, I need to keep Sprite with the shapes as long as
they are needed for info such as height and width. Therefore, when I dispose
of the Sprite (when I close the window it is attached to), I dispose of the
shapes as well. Notice the resource has already been released after the call
to SpriteCreate, it's only valid in the context of sprite creation, it's no
longer valid afterwards.*/
void SpriteDelete(SpritePtr spr)
{
unsigned int i;
for (i=0; i<spr->nshapes; i++)
{
free(spr->shapes(i));
}
free(spr->shapes);
free(spr);
}
/*
* main.c
* ViewRLE
*
* Created by Zacha Pedro on some date.
* Copyright Š 2005 the EVDC. No right reserved.
*
*/
/* Note: it's quite important to make sure this file is saved in MacRoman
encoding, and that you check the accents (here, the ë's) are not corrupted,
as otherwise this code won't work properly.*/
#include "RLErsrc.h"
static const float CGBlack(4)={0,0,0,1};
/* The window defined in the nib, that contains the controls.*/
WindowRef mainWindow;
/* These globals reference the resource IDs of the rlë8 and rlëD available
in the opened resource file, for the popup menu.*/
SInt16 rle8Count, rleDCount;
SInt16 *rle8IDList, *rleDIDList;
void Initialisation(void);
extern SpritePtr SpriteCreate(Handle hand, ControlRef controle);
extern void SpriteDelete(SpritePtr);
void InvoquerFenetre(SpritePtr spr);
void InstallerTimer(SpritePtr spr);
pascal OSStatus WindowCloseEventHandler(EventHandlerCallRef handlerRef,
EventRef event, void *userData);
pascal OSStatus WindowClickEventHandler(EventHandlerCallRef handlerRef,
EventRef event, void *userData);
pascal OSStatus CommandEventHandler( EventHandlerCallRef handlerRef, EventRef event, void *userData );
pascal void AvanceFrame (EventLoopTimerRef theTimer, void* userData);
static void ReleaseData(void *info, const void *data, size_t size);
int main(int argc, char* argv())
{
IBNibRef nibRef;
OSStatus err;
const EventTypeSpec commande={kEventClassCommand,kEventCommandProcess};
// Create a Nib reference passing the name of the nib file (without
// the .nib extension).
// CreateNibReference only searches into the application bundle.
err = CreateNibReference(CFSTR("main"), &nibRef);
require_noerr( err, CantGetNibRef );
// Once the nib reference is created, set the menu bar.
// "MainMenu" is the name of the menu bar object. This name
// is set in InterfaceBuilder when the nib is created.
err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"));
require_noerr( err, CantSetMenuBar );
// Then create a window. "MainWindow" is the name of the window object.
// This name is set in InterfaceBuilder when the nib is created.
err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &mainWindow);
require_noerr( err, CantCreateWindow );
// We don't need the nib reference anymore.
DisposeNibReference(nibRef);
// The window was created hidden so show it.
ShowWindow( mainWindow );
Initialisation();
InstallApplicationEventHandler(NewEventHandlerUPP(CommandEventHandler),
1, &commande ,NULL,NULL);
// Call the event loop
RunApplicationEventLoop();
CantCreateWindow:
CantSetMenuBar:
CantGetNibRef:
return err;
}
/* Intialisation is in charge of setting up everything. Which ended
up being MUCH as I progressively added stuff here. Perhaps I should
break this up a bit. You only have to check the list of local vars
to see why.*/
void Initialisation(void)
{
FSRef resFile;
NavDialogCreationOptions optionNav;
NavDialogRef dialogue;
NavUserAction action;
NavReplyRecord resultat;
AEKeyword toTrash1;
DescType toTrash2;
Size toTrash3;
SInt16 resFileRefNum, a;
Boolean endWhile;
Handle resHandle;
ResType typeToTrash;
Str255 pString;
SInt16 i;
CFStringRef curCFStr;
MenuRef leMenu;
ControlID idControle={'Misa',-1};
ControlRef leControle;
HFSUniStr255 uniName;
InitCursor();
/*freopen("/stdout_ViewRLE.txt","a",stdout);*/ // uncomment for debug log file.
printf("Beginning log file...\n");
/* First, ask the user for the file to open. Currently, it's only done
when launching the app and you can't change while running.*/
/* asking the user a file is not as simple as:
fileToOpen=AskUserAFile();
Indeed, you need to create a dialog with many parameters, then invoke
it, then get the reply structure, then get the file reference.*/
NavGetDefaultDialogCreationOptions(&optionNav);
optionNav.optionFlags= (kNavNoTypePopup |
kNavDontAutoTranslate |
kNavDontAddTranslateItems |
kNavAllowStationery |
kNavAllowInvisibleFiles |
kNavSelectAllReadableItem |
kNavSupportPackages |
kNavAllowOpenPackages);
optionNav.modality = kWindowModalityAppModal;
optionNav.message = CFSTR("Please pick a data/plug-in file containing rle data");
/* I loop as long as a valid file hasn't been picked. The above is constant, so
I don't need to redo it.*/
do
{
/* However, I change the message as a simple feedback to the user, so I need
to recreate the dialog each time instead of reusing the same.*/
NavCreateGetFileDialog (&optionNav,
NULL,
NULL,
NULL,
NULL,
NULL,
&dialogue);
/* Run the dialog...*/
NavDialogRun(dialogue);
action = NavDialogGetUserAction(dialogue);
/* If the user cancelled, I consider he doesn't want to view rlë's anymore
so I quit this app.*/
if (action==kNavUserActionNone||action==kNavUserActionCancel)
{
ExitToShell();
}
NavDialogGetReply(dialogue, &resultat);
/* Same if incomplete reply.*/
if ( !resultat.validRecord )
{
ExitToShell();
}
/* Then use Apple Event data retrieving mechanisms (Navigation Services
uses AE data packing and coercing/casting mechanisms) to get the
file system reference to the file to open.*/
AEGetNthPtr(&(resultat.selection), 1, typeFSRef,
&toTrash1, &toTrash2, &resFile,
sizeof(FSRef), &toTrash3);
NavDisposeReply(&resultat);
NavDialogDispose(dialogue);
/* The I open the resource fork of the file.*/
resFileRefNum=FSOpenResFile(&resFile,fsRdPerm);
if (resFileRefNum==-1) // see strings.
{
printf("Resource file/fork not found.\n");
optionNav.message=CFSTR(
"The file you picked does not seem to have a resource fork. Please select another");
endWhile=0;
}
else /* otherwise, detect if there are actual rlë resources. For instance,
some Nova Ships files do not have any rlë, so this is catched before
going on so that the user doesn't realise it afterwards and have to
quit the app and relaunch it.*/
{
rleDCount=Count1Resources('rlëD');
rle8Count=Count1Resources('rlë8');
if (rle8Count+rleDCount==0)
{
printf("No rle resource whatsoever found.\n");
optionNav.message=CFSTR(
"There does not seem to be any rle8 or rleD resource in the file you picked. Please select another.");
endWhile=0;
}
else
{
printf("Valid rle-containing resource file found\n");
endWhile=1;
}
}
}
while (!endWhile); // repeat as long as a valid file hasn't been found.
/* Initialise the global lists.*/
if (rle8Count!=0)
{
rle8IDList=malloc(rle8Count*sizeof(short));
}
if (rleDCount!=0)
{
rleDIDList=malloc(rleDCount*sizeof(short));
}
/* Then load the resource IDs of all the resources. For this, I need to get
a handle to the resource while not loading it (the handle points then
to NULL, meaning the resource isn't loaded), so that I can get the info
for the resource, which include its ID.*/
SetResLoad(FALSE);
for (i=0; i<rle8Count; i++)
{
resHandle=Get1IndResource('rlë8', i+1); // the index is 1-based.
GetResInfo(resHandle, rle8IDList+i, &typeToTrash, pString);
}
for (i=0; i<rleDCount; i++)
{
resHandle=Get1IndResource('rlëD', i+1);
GetResInfo(resHandle, rleDIDList+i, &typeToTrash, pString);
}
SetResLoad(TRUE);
/* Oops! the resource ID list isn't sorted! Let's sort it.*/
/* I apply an exchange sorting algorithm.*/
i=0;
while (i<rle8Count-1)
{
if (rle8IDList(i)<=rle8IDList(i+1))
{
i++;
}
else
{
a=rle8IDList(i);
rle8IDList(i)=rle8IDList(i+1);
rle8IDList(i+1)=a;
(i==0 ? i++ : i--);
}
}
i=0;
while (i<rleDCount-1)
{
if (rleDIDList(i)<=rleDIDList(i+1))
{
i++;
}
else
{
a=rleDIDList(i);
rleDIDList(i)=rleDIDList(i+1);
rleDIDList(i+1)=a;
(i==0 ? i++ : i--);
}
}
/* Then I add the menu items with text being the resource IDs.*/
/* There are two popup menu controls with two associated popup menus.
Their visibilities are swapped when the radio buttons are changed,
to give the illusion the popup menu and its items have changed.
The rlëD radio button is selected initially, so the rlë8 popup
menu control is initially hidden and the rlëD one is shown.*/
if (rle8Count!=0) // if there is no rle8 resource, leave the popup menu alone.
{
idControle.id=41; // the popup menu with the rle8 resource IDs
GetControlByID(mainWindow, &idControle, &leControle);
leMenu=GetControlPopupMenuHandle(leControle);
DeleteMenuItem(leMenu, 1); // the "none" menu item
SetResLoad(FALSE);
for (i=0; i<rle8Count; i++)
{
GetResInfo(Get1Resource('rlë8', rle8IDList(i)), &a, &typeToTrash, pString);
// for the resource name.
if (pString(0)!=0)
{
AppendMenuItemText(leMenu, pString);
}
else
{
curCFStr=CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), rle8IDList(i));
AppendMenuItemTextWithCFString(leMenu, curCFStr, 0, 0, NULL);
CFRelease(curCFStr);
}
}
SetResLoad(TRUE);
/* The popup menu control that owns the popup menu returns the index of
the selected menu item. For the control to behave well, I need to
update the maximum possible value to the actual number of menu items.
Otherwise, I can't select items past the initial number of menu items
(I mean, really, you can try).*/
SetControl32BitMaximum(leControle, rle8Count);
/* Lastly, redraw the control, since the underlying menu has been changed.*/
Draw1Control(leControle);
}
if (rleDCount==0) // if it's 0, then select the rle8 radio button and switch popup menus.
{
idControle.id=21; // the radio buttons group
GetControlByID(mainWindow, &idControle, &leControle);
SetControl32BitValue(leControle, 1);
Draw1Control(leControle);
idControle.id=41; // the popup menu with the rle8 resource IDs
GetControlByID(mainWindow, &idControle, &leControle);
SetControlVisibility(leControle, 1,1);
idControle.id=42; // the popup menu with the rleD resource IDs
GetControlByID(mainWindow, &idControle, &leControle);
SetControlVisibility(leControle, 0,1);
}
else // Otherwise, do the same as above.
{
idControle.id=42; // the popup menu with the rleD resource IDs
GetControlByID(mainWindow, &idControle, &leControle);
leMenu=GetControlPopupMenuHandle(leControle);
DeleteMenuItem(leMenu, 1); // the "none" menu item
SetResLoad(TRUE);
for (i=0; i<rleDCount; i++)
{
GetResInfo(Get1Resource('rlëD', rleDIDList(i)), &a, &typeToTrash, pString);
// for the resource name.
if (pString(0)!=0)
{
AppendMenuItemText(leMenu, pString);
}
else
{
curCFStr=CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), rleDIDList(i));
AppendMenuItemTextWithCFString(leMenu, curCFStr, 0, 0, NULL);
CFRelease(curCFStr);
}
}
SetResLoad(TRUE);
SetControl32BitMaximum(leControle, rleDCount);
Draw1Control(leControle);
}
// Lastly, set the top static text with the file name.
// So far, I've not been able to get it to work.
/*FSGetCatalogInfo(&resFile,kFSCatInfoNone, NULL, &uniName, NULL, NULL);
curCFStr=CFStringCreateWithCharacters(NULL, uniName.unicode, uniName.length);
CFShow(curCFStr);
idControle.id=3;
GetControlByID(mainWindow, &idControle, &leControle);
SetControlData(leControle, kControlEntireControl, 'cfst',
4,&curCFStr);
CFRelease(curCFStr);*/
// Looks like I'm done. Yehee!
}
void InvoquerFenetre(SpritePtr spr)
{
Rect scrRect, wRect;
static short sCurX=-1000, sCurY=-1000, sNextX=-1000, sNextY=-1000;
/* Not on ANY screen for sure... it will be considered outside of the current screen
and therefore be initialised with 7, 28 from the top left of the current screen,
and from then on incremented, sCurY first, then sCurX with sNextX so that the
windows don't overlap.*/
WindowRef thisWindow;
Str255 resName;
SInt16 resID;
ResType type;
CFStringRef string;
GDHandle device;
ControlID idControle={'Misa',-1};
ControlRef leControle;
const EventTypeSpec typeList(1)={ {kEventClassWindow,kEventWindowClose} },
mouseClickList(1)={ {kEventClassWindow, kEventWindowHandleContentClick}};
/* First, try to position the window. I get the screen where the main window
is, then progressively fill space in this screen.*/
GetWindowGreatestAreaDevice(mainWindow, kWindowStructureRgn, &device, &wRect);
GetAvailableWindowPositioningBounds(device, &scrRect);
SetRect(&wRect,
sCurX, sCurY,
sCurX+spr->width,sCurY+spr->height);
/* left, top, right, bottom */
if (!PtInRect( ((Point*)&wRect)(1)/* the lower-right corner*/, &scrRect))
{
/* First case: the window would extend below the acceptable region of the screen.
In which case I make a rectangle that starts at sNextX, sNextY (that is, I go
to the "following" column).*/
sCurX=sNextX;
sCurY=sNextY;
SetRect(&wRect,
sCurX, sCurY,
sCurX+spr->width,sCurY+spr->height);
if (!PtInRect( ((Point*)&wRect)(1)/* the lower-right corner*/, &scrRect))
{
/* If it's ~still~ not on the screen, then this means either:
- the user changed the screen in which the main window is (which includes
the starting case, with the initial values of sCur/sNext)
- there's no more room on the current screen
either way, restart from the screen's top left corner.*/
sCurX=scrRect.left+7;
sCurY=scrRect.top+28;
sNextX=sCurX;
SetRect(&wRect,
sCurX, sCurY,
sCurX+spr->width,sCurY+spr->height);
}
}
/* Now, create the window at that point,*/
CreateNewWindow(kDocumentWindowClass,
kWindowCloseBoxAttribute |
kWindowCollapseBoxAttribute |
kWindowStandardHandlerAttribute ,
&wRect,
&thisWindow);
/* and update the static vars for next time, that is, normally below the
window just created, and make sure enough room is left on the right
when the next column will be colonised.*/
sCurY+=spr->height+26;
if (sNextX < sCurX+spr->width+5)
{
sNextX= sCurX+spr->width+5;
}
sNextY=scrRect.top+28;
/* Then, set the window's title. Either I have a resource name and I put it,
or I put the resource type and ID.*/
GetResInfo(spr->resHandle, &resID, &type, resName);
if (resName(0)==0)
{
string=CFStringCreateWithFormat(NULL, NULL, CFSTR("%c%c%c%c ID %d"),
((char*)&type)(0),
((char*)&type)(1),
((char*)&type)(2),
((char*)&type)(3),
resID);
SetWindowTitleWithCFString(thisWindow,string);
CFRelease(string);
}
else
{
SetWTitle(thisWindow, resName);
}
/* Then I install the event handlers: the one that will delete the Sprite
when closing, and the one that allows stopping/resuming when clicking
on the content.*/
InstallWindowEventHandler(thisWindow,
NewEventHandlerUPP(
(EventHandlerProcPtr)WindowCloseEventHandler),
1, typeList,
(void*)spr, NULL);
InstallWindowEventHandler(thisWindow,
NewEventHandlerUPP((EventHandlerProcPtr)WindowClickEventHandler),
1, mouseClickList, spr, NULL);
spr->window=thisWindow;
/* Lastly, I apply a neat Appearance effect: I zoom the window from the
ID popup menu. Indeed, all windows are created equal... err, scratch
that, all windows are created hidden, and I need to show it.*/
/* First I get its window-local bounds,*/
idControle.id=41; // the popup menu
GetControlByID(mainWindow, &idControle, &leControle);
GetControlBounds(leControle, &wRect);
/* then I translate it into global coordinates,*/
GetWindowBounds(mainWindow, kWindowContentRgn, &scrRect);
wRect.top+=scrRect.top;
wRect.left+=scrRect.left;
wRect.bottom+=scrRect.top;
wRect.right+=scrRect.left;
/* and I apply the effect.*/
TransitionWindow(thisWindow,
kWindowZoomTransitionEffect,
kWindowShowTransitionAction,
&wRect);
/* I put the main window back as the current, active window, as the
window just created does not need to be selected.*/
SelectWindow(mainWindow);
}
/* This event handler is in charge of releasing the sprite and removing
the timer associated with it and its window, before the window is actually closed.*/
pascal OSStatus WindowCloseEventHandler(EventHandlerCallRef handlerRef,
EventRef event, void *userData)
{
RemoveEventLoopTimer(((SpritePtr)userData)->timerToRemove);
SpriteDelete(userData);
return eventNotHandledErr;
}
/* This event handler is in charge of toggling between rotation and stop.*/
pascal OSStatus WindowClickEventHandler(EventHandlerCallRef handlerRef,
EventRef event, void *userData)
{
SpritePtr spr=userData;
if (spr->running)
{
spr->running=0;
SetEventLoopTimerNextFireTime(spr->timerToRemove, kEventDurationForever);
}
else
{
spr->running=1;
SetEventLoopTimerNextFireTime(spr->timerToRemove, kEventDurationNoWait);
}
return eventNotHandledErr;
}
/* This timer is in charge of rotating the stuff.*/
pascal void AvanceFrame (EventLoopTimerRef theTimer, void* userData)
{
CGDataProviderRef dataObject;
CGImageRef imageObject;
CGContextRef curContext;
SpritePtr spr=userData;
/* First, increase the currently displayed shape data, */
(spr->currentlyDisplayedShape)++;
if (spr->currentlyDisplayedShape>=spr->nshapes)
{
spr->currentlyDisplayedShape=0;
}
/* then draw.*/
QDBeginCGContext(GetWindowPort(spr->window),&curContext);
dataObject=CGDataProviderCreateWithData (NULL,
spr->shapes(spr->currentlyDisplayedShape),
spr->width*spr->height*4, &ReleaseData);
imageObject= CGImageCreate(spr->width,spr->height,8,32,spr->width*4,
CGColorSpaceCreateDeviceRGB(),
kCGImageAlphaLast,
dataObject, NULL,0,
kCGRenderingIntentDefault);
CGContextSetFillColorSpace(curContext, CGColorSpaceCreateDeviceRGB());
CGContextSetFillColor(curContext,CGBlack);
CGContextFillRect(curContext,
CGRectMake(0.0,0.0,spr->width,spr->height));
CGContextDrawImage(curContext,
CGRectMake(0.0,0.0,spr->width,spr->height),
imageObject);
CGImageRelease(imageObject);
CGDataProviderRelease(dataObject);
CGContextSynchronize(curContext);
QDEndCGContext(GetWindowPort(spr->window),&curContext);
}
void InstallerTimer(SpritePtr spr)
{
InstallEventLoopTimer (GetMainEventLoop(),
kEventDurationSecond/10,
kEventDurationSecond/10,
NewEventLoopTimerUPP(AvanceFrame),
(void*)spr,
&(spr->timerToRemove));
}
/* This handler handles command events, which are mainly two: the command sent by
the "Load It!" push button, and the command sent when toggling the radio buttons,
to swap the two popup menu controls.*/
pascal OSStatus CommandEventHandler( EventHandlerCallRef handlerRef, EventRef event, void *userData )
{
HICommandExtended laCommande;
OSStatus result=noErr; // Unless otherwise set, I handle the event.
ControlID idControle={'Misa',-1};
ControlRef leControle;
SInt16 resID;
SInt32 radio;
Handle RLEres;
SpritePtr thisSprite;
/* The command identifier, a four-char-code, is a field of a parameter of the event.*/
GetEventParameter(event,kEventParamDirectObject,
typeHICommand,NULL,
sizeof(HICommandExtended),NULL,
&laCommande);
if (laCommande.commandID>='aaaa')
{
result=eventNotHandledErr;
}
else
{
switch (laCommande.commandID)
{
case 'Cmnd': // Load It! button.
idControle.id=2; // the hidden static text field
GetControlByID(mainWindow, &idControle, &leControle);
SetControlData(leControle, kControlEntireControl, 'text', 0,"");
SetControlVisibility(leControle, 0, 1);
idControle.id=21; // the radio buttons group
GetControlByID(mainWindow, &idControle, &leControle);
// the following returns me the index of the currently selected radio button.
radio=GetControl32BitValue(leControle);
if (radio==1) // rlë8 is set
{
if (rle8Count==0)
{
idControle.id=2; // the hidden static text field
GetControlByID(mainWindow, &idControle, &leControle);
SetControlData(leControle, kControlEntireControl, 'text',
17,"No rlë8 resource!");
SetControlVisibility(leControle, 1, 1);
return result;
}
else
{
idControle.id=41; // the popup menu with the rlë8 resource IDs
GetControlByID(mainWindow, &idControle, &leControle);
resID=GetControl32BitValue(leControle);
/* The above returns me the index of the menu item currently
selected, but I fetch the resourceID from the global buffer
for efficiency (instead of getting the menu item text and
converting it as an integer).*/
resID=rle8IDList(resID-1);
}
}
else // rlëD is set
{
if (rleDCount==0)
{
idControle.id=2; // the hidden static text field
GetControlByID(mainWindow, &idControle, &leControle);
SetControlData(leControle, kControlEntireControl, 'text',
17,"No rlëD resource!");
SetControlVisibility(leControle, 1, 1);
return result;
}
else
{
idControle.id=42; // the popup menu with the rlëD resource IDs
GetControlByID(mainWindow, &idControle, &leControle);
resID=GetControl32BitValue(leControle);
/* The above returns me the index of the menu item currently
selected, but I fetch the resourceID from the global buffer
for efficiency (instead of getting the menu item text and
converting it to an integer).*/
resID=rleDIDList(resID-1);
}
}
RLEres=GetResource((radio==1 ? 'rlë8' : 'rlëD'), resID);
if (RLEres==NULL) // Theoretically, this shouldn't happen...
{
idControle.id=2; // the hidden static text field
GetControlByID(mainWindow, &idControle, &leControle);
SetControlData(leControle, kControlEntireControl, 'text',
19,"Resource not found!");
SetControlVisibility(leControle, 1, 1);
}
else // actually load the stuff.
{
idControle.id=11; // the hidden progress bar
GetControlByID(mainWindow, &idControle, &leControle);
SetControlVisibility(leControle, 1, 1);
thisSprite=SpriteCreate(RLEres, leControle);
SetControlVisibility(leControle, 0, 1);
InstallerTimer(thisSprite);
ReleaseResource(RLEres);
}
break;
case 'RaSw':
idControle.id=21; // the radio buttons group
GetControlByID(mainWindow, &idControle, &leControle);
radio=GetControl32BitValue(leControle);
if (radio==2) // it's now rlëD.
{
idControle.id=42; // the popup menu with the rleD resource IDs
GetControlByID(mainWindow, &idControle, &leControle);
SetControlVisibility(leControle, 1,1); // show it.
idControle.id=41; // the popup menu with the rle8 resource IDs
GetControlByID(mainWindow, &idControle, &leControle);
SetControlVisibility(leControle, 0,1); // hide it.
}
else // it's now rlë8
{
idControle.id=41; // the popup menu with the rle8 resource IDs
GetControlByID(mainWindow, &idControle, &leControle);
SetControlVisibility(leControle, 1,1);
idControle.id=42; // the popup menu with the rleD resource IDs
GetControlByID(mainWindow, &idControle, &leControle);
SetControlVisibility(leControle, 0,1);
}
result=eventNotHandledErr;
break;
default:
result=eventNotHandledErr;
break;
}
}
return result;
}
static void ReleaseData(void *info, const void *data, size_t size)
{
/* ReleaseData will be passed to CGDataProviderCreateWithData,
and is assumed to free the memory itself when CGDataProvider is
relased, but it is to do ABSOLUTELY NOTHING. Indeed, I need to
release the CGDataProvider since I'm not allowed to change the
memory it references while it exists, but I need to keep the memory
buffer! */
}
This post has been edited by Zacha Pedro : 06 June 2005 - 09:20 AM
Any chance of getting a PC version of this program? Seems like it would be fun to tinker with.
No, sorry. I really don't know Win32 programming, not to mention I don't have any Win machine I could code it on. Bryce's code is better to start from in case a PC version, in fact, I just added mac-specific stuff.
Zacha Pedro, on May 16 2005, 10:07 AM, said:
No, sorry. I really don't know Win32 programming, not to mention I don't have any Win machine I could code it on. Bryce's code is better to start from in case a PC version, in fact, I just added mac-specific stuff.
View Post
Darn :mad: Oh well. If someone ever does devel a win32 app for this, go ahead and send it my way, diehard2k0@yahoo.co.uk. Would be appreciated. I really don't like Apple computers, but don't get me wrong, i hate windows too, but it plays more of the games i like without emulation. I might concider taking on the task myself and make it a win32 app....... dunno. If i do, i will post it up here when/if i do it.
w00t, ZP, but do you think you could release a version so that we don't have to make a ViewRLE.app and put Contents inside it?
This post has been edited by orcaloverbri9 : 17 May 2005 - 09:13 PM
Oops, do you mean the zip I put expands into a contents folder without the surrounding .app?! I compressed that suff on a PC (forgot to do it on my Mac before coming to school where I have to access Internet from), and sure enough that PowerArchiver thought the ViewRLE.app to be unnecessary and ditched it. Let me correct that at once.
EDIT: Okay, I think it's fixed now. Thank God for Linux. By the way, This is the third behavior I've met for codeboxes with too long lines: IE on Win2k seems to wrap the lines, IE on XP extends the page and forces an horizontal scroll bar at the bottom, and Konqueror adds an horizontal scroll bar to the codebox! Not to mention some respect the indenting and others not...
By the way, any comment on this? Perhaps I should add some features (say, ability to open another file, drag-and-drop a plug-in to it), a ReadMe, and upload it to the add-on files?
This post has been edited by Zacha Pedro : 18 May 2005 - 11:07 AM
It would be awesome, that's for sure. It seems fairly silly to open an editor to view an RLE.
I added support to drag-and-drop, only when the app is not already launched though (i.e. still only one file at a time), unless of course you arrange so that the app doesn't prompt you for a file when opening, a behavior it has when run through the Apple Script "launch" command, which runs an app without sending it any open app or open docs event, while normally launching an app sends at least one of these. And whatever, in this case you can always pick file->open... .
So you can keep it in your Dock and drag and drop your plugs/data files to it! notice that it does the same checks as when it asks for a file: if there in no resource fork or no rlë data in the resource fork in the file you dragged, it reverts to asking a file instead; in this case you can press cancel to revert to "waiting for a file" mode so you can drag and drop another, so that you don't have to browse your drive through Navigation Services (the open file dialog).
Notice this hasn't been quick to implement as I had to learn everything about Apple Events - a subject I completely ignored before. It will be useful in the long run, though.
Just leave me a few hours to get the build from my iPod Shuffle to a PC and .zip it appropriately...
EDIT: Oops, almost forgot to do it today... Done.
EDIT2: removed buggy zip.
This post has been edited by Zacha Pedro : 26 May 2005 - 07:14 AM
Thanks for doing something with my code!
I'm not suprised that my solid color token implementation had a bug, as I wasn't able to find any examples of it use in a RLE to test my code with. Thanks for finding and squishing it. What is rEV? (I'm a bit out of the loop on the whole EV development thing, sorry.) Anyway, I'm glad my code stood up under fire otherwise and was able to correctly interpret giant sprites.
C++ probably isn't justified for such a small project, but the code I posted is part of a much larger (and secret... shhh :D) project.
On the topic of a PC version: The Linux code should work perfectly fine. Just as long as you have SDL installed, you should be able to compile and run it on Windows. Of course you'll want to modify my code so that it doesn't have the aforementioned bug. (I'll do that myself someday on my copy of the code at home.)
This post has been edited by Bryce : 23 May 2005 - 05:13 PM
Oh crap. It's the last time I attempt to use zip, from now on I'll use dmgs for OSX distribution.
...
F*CK THIS STUPID IPB! Whatever the extension I put (unless it's some silly one such as .gif or .txt, that would be misinterpreted by your Mac), it tells me I can't upload a file with that extension! I ~HATE~ extensions and all the systems designed around them!
...
Okay, found a solution. I further zipped the dmg. It ought to work. If it doesn't, I think we don't have any solution other than sacrifice to the IPB Gods to try and appease them, so that they accept more extensions (or have all extensions accepted by default, and only some blacklisted).
EDIT: Removed old build. Please download the latest.
Bryce, on May 23 2005, 11:06 PM, said:
Thanks for doing something with my code!
I'm not suprised that my solid color token implementation had a bug, as I wasn't able to find any examples of it use in a RLE to test my code with. Thanks for finding and squishing it. What is rEV? (I'm a bit out of the loop on the whole EV development thing, sorry.) Anyway, I'm glad my code stood up under fire otherwise and was able to correctly interpret giant sprites.
C++ probably isn't justified for such a small project, but the code I posted is part of a much larger (and secret... shhh :D) project.
On the topic of a PC version: The Linux code should work perfectly fine. Just as long as you have SDL installed, you should be able to compile and run it on Windows. Of course you'll want to modify my code so that it doesn't have the aforementioned bug. (I'll do that myself someday on my copy of the code at home.)
rEV is short for rEVisited, a graphical replacement-mostly plug-in for the classic port for Nova, check this topic.
This post has been edited by Zacha Pedro : 06 June 2005 - 09:17 AM
Ah, that works. Unzips and mounts by itself.
Zacha Pedro, on May 26 2005, 12:11 PM, said:
rEV is short for rEVisited, a graphical replacement-mostly plug-in for the classic port for Nova, check this topic.
View Post
Wow, I never knew this was out! Ah, I see it was on the EV boards. Nuts, looks like stuffit 9.0.1 doesn't work with it.