[.net] Reading Two Bytes as One

Started by
15 comments, last by binchawpz 15 years, 3 months ago
Just a note. All your lines of:
if(buffer[pos + 1] == 'A'){right = toupper(buffer[pos + 1]) - 'A' + 0xA; }if(buffer[pos + 1] == 'B'){right = toupper(buffer[pos + 1]) - 'B' + 0xB; }if(buffer[pos + 1] == 'C'){right = toupper(buffer[pos + 1]) - 'C' + 0xC; }if(buffer[pos + 1] == 'D'){right = toupper(buffer[pos + 1]) - 'D' + 0xD; }if(buffer[pos + 1] == 'E'){right = toupper(buffer[pos + 1]) - 'E' + 0xE; }if(buffer[pos + 1] == 'F'){right = toupper(buffer[pos + 1]) - 'F' + 0xF;
Result in a canceling out of your first two terms as you just end up adding the hex value at the end. This is because the value in your buffer has already been determined to be the value you are subtracting (IE 'C' - 'C' = 0). Also, you are making specific checks to see if the value is an uppercase character so your toupper call isn't going to do much.

You can cut all of these checks down to a simple
right = buffer[pos + 1] - 'A' + 0x0A;
This will subtract the ascii value in your buffer from the ascii value of 'A' (IE 'C' - 'A' = 2) and add that value to the hex value 0x0A ('C' - 'A' + 0x0A = 0x0C).

Earlier, it was suggested to use toupper so that if you had any lower case characters in your file they would be converted to upper case. This would make it so that you could subtract 'A' in all cases instead of having to subtract lower case 'a' (a different ASCII value) if some characters were lower case.

Doesn't matter in the long run, but it'll make your code a little cleaner.
Advertisement
I really don't understand what you're trying to do. It looks to me like you are torturing yourself with this haphazard method. Some files are stored as bytes. Read bytes. Some files are stored as integers. Read integers. Reading in 2 bytes as an integer is as simple as fread(buffer, 4, 1, file).
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------I once read that World of Warcraft is poor fishing simulator. I don't know about you but I catch a lot more fish in World of Warcraft than I do in real life.
Quote:Original post by kittycat768
I really don't understand what you're trying to do. It looks to me like you are torturing yourself with this haphazard method. Some files are stored as bytes. Read bytes. Some files are stored as integers. Read integers. Reading in 2 bytes as an integer is as simple as fread(buffer, 4, 1, file).


fread is a legacy C function. The proper C++ solution is given by ToohrVyk above.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Quote:Original post by caldiar
turns out the alpha channel was skewing the byte order creating a pattern of red/green/blue instead of just red.


In the future, feel free to post (small) pictures to illustrate this kind of problem.

Quote:
Wow that's some elegant (ToohrVyk's) code.


Welcome to C++. :)

Quote:
void *safe_malloc( size_t size ){  void *p;  p = malloc(size);  if(!p)    printf("SAFE_MALLOC HAS AN ERROR!");  return p;}



OK. Many problems here.

1) This is C++. Use new. It's idiomatic.
2) This is C++. Use new. It's type-safe. malloc() is not.
3) This is C++. Use new. It doesn't require you to think about the size of the type of thing you want to allocate. malloc() does not.
4) This is C++. Use new and new[]. This keeps scalar and array allocations distinct (which may allow the compiler to make some optimizations in the underlying allocation system, and is useful for documentation purposes). malloc() cannot make such a distinction.
5) You already use new[] elsewhere in the code, so be consistent.
6) Actually, forget all of that, and use std::vector instead. Don't mess around with calling delete yourself. Using a standard library container helps immensely with exception safety.
7) Printing an error message locally doesn't make anything "safe". By writing the code this way, you (a) make the writer of the calling code (you) think that no check for a null return value is necessary (when it is); (b) deny the writer of the calling code (you) the option of *not* printing the message, or of formatting it differently, etc.

Only attempt to handle errors at the point where they can be handled. This is largely the point of exceptions in C++: to provide an easy way to get back to the point in the code where the problem can/should be handled.

Often, failed allocations shouldn't be handled at all, but should just let the program crash - in a controlled manner! - without letting other stuff execute. With new (and implicitly with standard library containers), this is easy to arrange: if there is a failure, a std::bad_alloc exception will be thrown, and all you do is - nothing. When no exception handler is found, std::terminate will be called, and you're done. new and new[] will never (in the normal form) return a NULL pointer (although you can use a special form that will, if you really need it).

As a result, we get:

// Oh, and don't use comments to disable code. Get a proper version control// system; then you can be aggressive about deleting bits, since you can// always get them back from the repository. Alternatively, use something like// #if 0// ...// #endif// for temporary disabling code.void FlipBytes(){	unsigned int printme;	// Please use C++ style casts in C++.	unsigned char *ptr = reinterpret_cast<unsigned char*>(&printme);	int i = 0;	// Please, do anything you can to make sure your editor is not allowed	// to mix tabs and spaces freely for indentation.	int left; // this was previously indented 4 spaces instead of a tab.	int right;	// In C++, we use std::fill to zero-initialize ranges (arrays,	// containers or portions thereof); but std::vector guarantees that the	// initial elements - if you request any - will be default-constructed	// (which makes them zero for primitive types).	std::vector<char> header(18);	header[2] = 2;	header[12] = 512 & 255; 	header[13] = 512 >> 8;	header[14] = 512 & 255; 	header[15] = 512 >> 8; 	header[16] = 24;	o_file.write(&header.front(), 18);	while (pos < length) {		//00 00 00 00		if (buffer[pos] <= '9'){			left = buffer[pos] - '0';		} else {			if(buffer[pos] == 'A'){left = toupper(buffer[pos]) - 'A' + 0xA; }			if(buffer[pos] == 'B'){left = toupper(buffer[pos]) - 'B' + 0xB; }			if(buffer[pos] == 'C'){left = toupper(buffer[pos]) - 'C' + 0xC; }			if(buffer[pos] == 'D'){left = toupper(buffer[pos]) - 'D' + 0xD; }			if(buffer[pos] == 'E'){left = toupper(buffer[pos]) - 'E' + 0xE; }			if(buffer[pos] == 'F'){left = toupper(buffer[pos]) - 'F' + 0xF; }		}		if(buffer[pos + 1] <= '9'){			right = buffer[pos + 1] - '0';		}else{			if(buffer[pos + 1] == 'A'){right = toupper(buffer[pos + 1]) - 'A' + 0xA; }			if(buffer[pos + 1] == 'B'){right = toupper(buffer[pos + 1]) - 'B' + 0xB; }			if(buffer[pos + 1] == 'C'){right = toupper(buffer[pos + 1]) - 'C' + 0xC; }			if(buffer[pos + 1] == 'D'){right = toupper(buffer[pos + 1]) - 'D' + 0xD; }			if(buffer[pos + 1] == 'E'){right = toupper(buffer[pos + 1]) - 'E' + 0xE; }			if(buffer[pos + 1] == 'F'){right = toupper(buffer[pos + 1]) - 'F' + 0xF; }		}		char newbyte = (char)(left * 16 + right);		o_file << newbyte;		ptr[i++] = newbyte; // similarly here with spaces vs. tabs.		// Also notice that array indexing and pointer arithmetic		// are equivalent.		if(i >= sizeof(unsigned int)){			i = 0;		}		pos += 3;	}}


Of course, if we notice that

(a) Using globals for something like this is really horrible (this is exactly what function parameters are for: to pass information into the function);
(b) There's no point in constructing the "printme" value any more;
(c) Each hex "nibble" can be converted in the same way, and making a function to convert each one would probably be cleaner;
(e) We could probably use a similar approach to write numbers into the header;
(d) 'A' through 'F' are consecutive in ASCII;
(e) Variables should be declared near first use and scoped as tightly as possible;

we get something more like:

template <int size>void writeBigEndian(char* where, int what) {	// We make a local copy of 'what', which is modified...	for (char* c = where + size; c > where; --c) {		// 'c' points one past where we want to write.		c[-1] = what & 255;		what >>= 8;	}}int as_hex(char c) {	if (c >= '0' && c <= '9') { return c - '0'; }	if (c >= 'A' && c <= 'F') { return c - 'A' + 0xA; }	if (c >= 'a' && c <= 'a') { return c - 'a' + 0xA; }	throw std::domain_error();}void FlipBytes(std::ofstream& o_file, std::vector<char> buffer, std::vector<char>::iterator pos){	std::vector<char> header(18);	header[2] = 2;	writeBigEndian<2>(header + 12, 512);	writeBigEndian<2>(header + 14, 512);	header[16] = 24;	o_file.write(&header.front(), 18);	while (pos != buffer.end()) {		int left = as_hex(*pos);		int right = as_hex(*(pos + 1));		// Don't need a temporary for this.		o_file << char(left * 16 + right);		pos += 3;	}}


You know, assuming you still want to do all the work, when it can be done like ToohrVyk showed... :)
Quote:Original post by kittycat768
I really don't understand what you're trying to do. It looks to me like you are torturing yourself with this haphazard method. Some files are stored as bytes. Read bytes. Some files are stored as integers. Read integers. Reading in 2 bytes as an integer is as simple as fread(buffer, 4, 1, file).


1) As pointed out, fread() belongs to C.
2) All files are stored as bytes. It is up to programs to interpret them as integers. A more accurate statement would be that some files are formatted such that they're intended to be interpreted as a series of integers. Of course, integer sizes and endian-ness are platform-specific.
3) You've missed the point, anyway: the file contains text which looks like what a hex editor would display for a binary file, and he wishes to convert this to the corresponding actual binary data. For example, if the file contains the two bytes '2' '0' in sequence, he would want to write a space character (ASCII 30, 0x20 in hex) to the file.

ToohrVyk's code does this, is blindingly short, and shows off the C++ standard library just as the language designers presumably intended (it would be hard to imagine coming up with classes like istream_iterator otherwise). I would probably use an ostreambuf_iterator for the output, though (ostream_iterator uses operator<<, which will happen to work fine for outputting chars, but doesn't document the intent very well). This kind of thing is more important to think about on the input side (if you use istream_iterator<char>, it will skip whitespace; but here, skipping whitespace is the desired behaviour).
I know my code is a mess and I typically don't program like that.

That's angry coding that you're seeing :D

the safe_malloc function is a function I borrowed from the source of another program that made use of it in a 24 bit tga writing function it had.

Well, the program works as intended though so it's all good now. Maybe... MAYBE if I get a wild hair up my butt I'll go back over the program and clean it.

Here's the resulting image on a successful run:


I was writing this program so I can have a greater control over the lightmaps used in Call of Duty 4. (For instance, say we have a texture that is a light with a grate over it. The light doesn't cast the shadows of the grate because they're both the same texture. Being able to pull out and recompile the lightmap data with custom painted lightmaps lets me paint the shadows of the grate that should appear)


Thanks again guys for all the help. I'm not used to writing this kind of stuff.

Also, just wondering, why was this moved to the .NET forum?
Quote:Original post by kittycat768
Legacy is a really nasty term used for things that are old. WinMain() has got to be like 15 years old by now at least. We sure need that legacy code to make our programs work. Stupid legacy code. Why can't they make an STL template to replace WinMain()... fread() belongs the stand C library. Everything in C is a part of C++. I really don't understand why people try to act like C is a dog and C++ is a cat. In reality C is a puppy and C++ is a dog. fread() is in no risk of becoming extinct anytime soon. Many things in C++ were designed to make things that you'd normally do in C easier. I don't see file reading getting any easier than fread(). If it isn't broken, don't fix it. Also, your wording "proper solution" is simply an opinion. This doesn't mean that I'm not open to learning how to open and read files with STL. I'm actually enjoying learning STL quite a bit. My main point is that fread() deserves our respect.


You are wrong. fread is not a part of C, it is a part of the C Standard Library. If you are using C++ you should not be using the C Standard Library, you should be using the C++ Standard Library and its subset the Standard Template Library. The proper way to read a file with C++ is to use streams, or more accurately file streams.

You are confusing the nature of C/C++ with the nature the C/C++ Standard Libraries. C++ was meant to be backwards compatible with C. This is purely in relation to the language itself. The standard libraries that accompany each language are in no way meant to be compatible.

This topic is closed to new replies.

Advertisement