Add SMX_SetLights2.
This is the same as SMX_SetLights, but instead of taking one buffer with a fixed size, it takes a separate buffer for each pad, and explicitly includes the size of the buffer rather than assuming it's 864 bytes. SMX_SetLights and SMX_SetLights2 call into the same underlying update.
This commit is contained in:
parent
d576545266
commit
303283624a
@ -68,6 +68,11 @@ SMX_API uint16_t SMX_GetInputState(int pad);
|
||||
// controlling lights should send light updates continually, even if the lights aren't changing.
|
||||
SMX_API void SMX_SetLights(const char lightsData[864]);
|
||||
|
||||
// This is the same as SMX_SetLights, but receives lights for each pad in separate
|
||||
// buffers of 432 colors each. lightsDataSize[pad] is the size of lightsData. If
|
||||
// it's not 432, that pad's lights will be left unchanged.
|
||||
SMX_API void SMX_SetLights2(const char *lightsData[2], int lightsDataSize[2]);
|
||||
|
||||
// By default, the panels light automatically when stepped on. If a lights command is sent by
|
||||
// the application, this stops happening to allow the application to fully control lighting.
|
||||
// If no lights update is received for a few seconds, automatic lighting is reenabled by the
|
||||
|
@ -60,6 +60,21 @@ SMX_API void SMX_FactoryReset(int pad) { SMXManager::g_pSMX->GetDevice(pad)->Fac
|
||||
SMX_API void SMX_ForceRecalibration(int pad) { SMXManager::g_pSMX->GetDevice(pad)->ForceRecalibration(); }
|
||||
SMX_API void SMX_SetTestMode(int pad, SensorTestMode mode) { SMXManager::g_pSMX->GetDevice(pad)->SetSensorTestMode((SensorTestMode) mode); }
|
||||
SMX_API bool SMX_GetTestData(int pad, SMXSensorTestModeData *data) { return SMXManager::g_pSMX->GetDevice(pad)->GetTestData(*data); }
|
||||
SMX_API void SMX_SetLights(const char lightsData[864]) { SMXManager::g_pSMX->SetLights(string(lightsData, 864)); }
|
||||
SMX_API void SMX_SetLights(const char lightsData[864])
|
||||
{
|
||||
string lights[] = {
|
||||
string(lightsData, 432),
|
||||
string(lightsData+432, 432),
|
||||
};
|
||||
SMXManager::g_pSMX->SetLights(lights);
|
||||
}
|
||||
SMX_API void SMX_SetLights2(const char *lightsData[2], int lightsDataSize[2])
|
||||
{
|
||||
string lights[] = {
|
||||
string(lightsData[0], lightsDataSize[0]),
|
||||
string(lightsData[1], lightsDataSize[1]),
|
||||
};
|
||||
SMXManager::g_pSMX->SetLights(lights);
|
||||
}
|
||||
SMX_API void SMX_ReenableAutoLights() { SMXManager::g_pSMX->ReenableAutoLights(); }
|
||||
SMX_API const char *SMX_Version() { return SMX_BUILD_VERSION; }
|
||||
|
@ -18,6 +18,7 @@
|
||||
<ClInclude Include="SMXDeviceConnection.h" />
|
||||
<ClInclude Include="SMXDeviceSearch.h" />
|
||||
<ClInclude Include="SMXDeviceSearchThreaded.h" />
|
||||
<ClInclude Include="SMXGif.h" />
|
||||
<ClInclude Include="SMXHelperThread.h" />
|
||||
<ClInclude Include="SMXManager.h" />
|
||||
<ClInclude Include="SMXThread.h" />
|
||||
@ -29,6 +30,7 @@
|
||||
<ClCompile Include="SMXDeviceConnection.cpp" />
|
||||
<ClCompile Include="SMXDeviceSearch.cpp" />
|
||||
<ClCompile Include="SMXDeviceSearchThreaded.cpp" />
|
||||
<ClCompile Include="SMXGif.cpp" />
|
||||
<ClCompile Include="SMXHelperThread.cpp" />
|
||||
<ClCompile Include="SMXManager.cpp" />
|
||||
<ClCompile Include="SMXThread.cpp" />
|
||||
|
@ -37,6 +37,9 @@
|
||||
<ClInclude Include="SMXThread.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SMXGif.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="SMX.cpp">
|
||||
@ -66,5 +69,8 @@
|
||||
<ClCompile Include="SMXThread.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SMXGif.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
530
sdk/Windows/SMXGif.cpp
Normal file
530
sdk/Windows/SMXGif.cpp
Normal file
@ -0,0 +1,530 @@
|
||||
#include "SMXGif.h"
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
using namespace std;
|
||||
|
||||
// This is a simple animated GIF decoder. It always decodes to RGBA color,
|
||||
// discarding palettes, and decodes the whole file at once.
|
||||
|
||||
class GIFError: public exception { };
|
||||
|
||||
struct Palette
|
||||
{
|
||||
SMXGif::Color color[256];
|
||||
};
|
||||
|
||||
void SMXGif::GIFImage::Init(int width_, int height_)
|
||||
{
|
||||
width = width_;
|
||||
height = height_;
|
||||
image.resize(width * height);
|
||||
}
|
||||
|
||||
void SMXGif::GIFImage::Clear(const Color &color)
|
||||
{
|
||||
for(int y = 0; y < height; ++y)
|
||||
for(int x = 0; x < width; ++x)
|
||||
get(x,y) = color;
|
||||
}
|
||||
|
||||
void SMXGif::GIFImage::CropImage(SMXGif::GIFImage &dst, int crop_left, int crop_top, int crop_width, int crop_height) const
|
||||
{
|
||||
dst.Init(crop_width, crop_height);
|
||||
|
||||
for(int y = 0; y < crop_height; ++y)
|
||||
{
|
||||
for(int x = 0; x < crop_width; ++x)
|
||||
dst.get(x,y) = get(x + crop_left, y + crop_top);
|
||||
}
|
||||
}
|
||||
|
||||
void SMXGif::GIFImage::Blit(SMXGif::GIFImage &src, int dst_left, int dst_top, int dst_width, int dst_height)
|
||||
{
|
||||
for(int y = 0; y < dst_height; ++y)
|
||||
{
|
||||
for(int x = 0; x < dst_width; ++x)
|
||||
get(x + dst_left, y + dst_top) = src.get(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
class DataStream
|
||||
{
|
||||
public:
|
||||
DataStream(const string &data_):
|
||||
data(data_)
|
||||
{
|
||||
}
|
||||
|
||||
uint8_t ReadByte()
|
||||
{
|
||||
if(pos >= data.size())
|
||||
throw GIFError();
|
||||
|
||||
uint8_t result = data[pos];
|
||||
pos++;
|
||||
return result;
|
||||
}
|
||||
|
||||
uint16_t ReadLE16()
|
||||
{
|
||||
uint8_t byte1 = ReadByte();
|
||||
uint8_t byte2 = ReadByte();
|
||||
return byte1 | (byte2 << 8);
|
||||
}
|
||||
|
||||
void ReadBytes(string &s, int count)
|
||||
{
|
||||
s.clear();
|
||||
while(count--)
|
||||
s.push_back(ReadByte());
|
||||
}
|
||||
|
||||
void skip(int bytes)
|
||||
{
|
||||
pos += bytes;
|
||||
}
|
||||
|
||||
private:
|
||||
const string &data;
|
||||
uint32_t pos = 0;
|
||||
};
|
||||
|
||||
class LWZStream
|
||||
{
|
||||
public:
|
||||
LWZStream(DataStream &stream_):
|
||||
stream(stream_)
|
||||
{
|
||||
}
|
||||
|
||||
// Read one LZW code from the input data.
|
||||
uint32_t ReadLZWCode(uint32_t bit_count)
|
||||
{
|
||||
while(bits_in_buffer < bit_count)
|
||||
{
|
||||
if(bytes_remaining == 0)
|
||||
{
|
||||
// Read the next block's byte count.
|
||||
bytes_remaining = stream.ReadByte();
|
||||
if(bytes_remaining == 0)
|
||||
throw GIFError();
|
||||
}
|
||||
|
||||
// Shift in another 8 bits into the end of self.bits.
|
||||
bits |= stream.ReadByte() << bits_in_buffer;
|
||||
bits_in_buffer += 8;
|
||||
bytes_remaining -= 1;
|
||||
}
|
||||
|
||||
// Shift out bit_count worth of data from the end.
|
||||
uint32_t result = bits & ((1 << bit_count) - 1);
|
||||
bits >>= bit_count;
|
||||
bits_in_buffer -= bit_count;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Skip the rest of the LZW data.
|
||||
void Flush()
|
||||
{
|
||||
stream.skip(bytes_remaining);
|
||||
bytes_remaining = 0;
|
||||
|
||||
// If there are any blocks past the end of data, skip them.
|
||||
while(1)
|
||||
{
|
||||
uint8_t blocksize = stream.ReadByte();
|
||||
stream.skip(blocksize);
|
||||
if(bytes_remaining == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
DataStream &stream;
|
||||
uint32_t bits = 0;
|
||||
int bytes_remaining = 0;
|
||||
int bits_in_buffer = 0;
|
||||
};
|
||||
|
||||
struct LWZDecoder
|
||||
{
|
||||
LWZDecoder(DataStream &stream):
|
||||
lzw_stream(LWZStream(stream))
|
||||
{
|
||||
// Each frame has a single bits field.
|
||||
code_bits = stream.ReadByte();
|
||||
}
|
||||
|
||||
string DecodeImage();
|
||||
|
||||
private:
|
||||
uint16_t code_bits;
|
||||
LWZStream lzw_stream;
|
||||
};
|
||||
|
||||
|
||||
static const int GIFBITS = 12;
|
||||
|
||||
string LWZDecoder::DecodeImage()
|
||||
{
|
||||
uint32_t dictionary_bits = code_bits + 1;
|
||||
int prev_code1 = -1;
|
||||
int prev_code2 = -1;
|
||||
|
||||
uint32_t clear = 1 << code_bits;
|
||||
uint32_t end = clear + 1;
|
||||
uint32_t next_free_slot = clear + 2;
|
||||
|
||||
vector<pair<int,int>> dictionary;
|
||||
dictionary.resize(1 << GIFBITS);
|
||||
|
||||
// We append to this buffer as we decode data, then append the data in reverse
|
||||
// order.
|
||||
string append_buffer;
|
||||
|
||||
string result;
|
||||
while(1)
|
||||
{
|
||||
// Flush append_buffer.
|
||||
for(int i = append_buffer.size() - 1; i >= 0; --i)
|
||||
result.push_back(append_buffer[i]);
|
||||
append_buffer.clear();
|
||||
|
||||
int code1 = lzw_stream.ReadLZWCode(dictionary_bits);
|
||||
// printf("%02x");
|
||||
if(code1 == end)
|
||||
break;
|
||||
|
||||
if(code1 == clear)
|
||||
{
|
||||
// Clear the dictionary and reset.
|
||||
dictionary_bits = code_bits + 1;
|
||||
next_free_slot = clear + 2;
|
||||
prev_code1 = -1;
|
||||
prev_code2 = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
int code2;
|
||||
if(code1 < next_free_slot)
|
||||
code2 = code1;
|
||||
else if(code1 == next_free_slot && prev_code2 != -1)
|
||||
{
|
||||
append_buffer.push_back(prev_code2);
|
||||
code2 = prev_code1;
|
||||
}
|
||||
else
|
||||
throw GIFError();
|
||||
|
||||
// Walk through the linked list of codes in the dictionary and append.
|
||||
while(code2 >= clear + 2)
|
||||
{
|
||||
uint8_t append_char = dictionary[code2].first;
|
||||
code2 = dictionary[code2].second;
|
||||
append_buffer.push_back(append_char);
|
||||
}
|
||||
append_buffer.push_back(code2);
|
||||
|
||||
// If we're already at the last free slot, the dictionary is full and can't be expanded.
|
||||
if(next_free_slot < (1 << dictionary_bits))
|
||||
{
|
||||
// If we have any free dictionary slots, save.
|
||||
if(prev_code1 != -1)
|
||||
{
|
||||
dictionary[next_free_slot] = make_pair(code2, prev_code1);
|
||||
next_free_slot += 1;
|
||||
}
|
||||
// If we've just filled the last dictionary slot, expand the dictionary size if possible.
|
||||
if(next_free_slot >= (1 << dictionary_bits) && dictionary_bits < GIFBITS)
|
||||
dictionary_bits += 1;
|
||||
}
|
||||
|
||||
prev_code1 = code1;
|
||||
prev_code2 = code2;
|
||||
}
|
||||
|
||||
// Skip any remaining data in this block.
|
||||
lzw_stream.Flush();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct GlobalGIFData
|
||||
{
|
||||
int width = 0, height = 0;
|
||||
int background_index = -1;
|
||||
bool use_transparency = false;
|
||||
int transparency_index = -1;
|
||||
int duration = 0;
|
||||
int disposal_method = 0;
|
||||
bool have_global_palette = false;
|
||||
Palette palette;
|
||||
};
|
||||
|
||||
class GIFDecoder
|
||||
{
|
||||
public:
|
||||
GIFDecoder(DataStream &stream_):
|
||||
stream(stream_)
|
||||
{
|
||||
}
|
||||
|
||||
void ReadAllFrames(vector<SMXGif::SMXGifFrame> &frames);
|
||||
|
||||
private:
|
||||
bool ReadPacket(string &packet);
|
||||
Palette ReadPalette(int palette_size);
|
||||
void DecodeImage(GlobalGIFData global_data, SMXGif::GIFImage &out);
|
||||
|
||||
DataStream &stream;
|
||||
SMXGif::GIFImage image;
|
||||
int frame;
|
||||
};
|
||||
|
||||
// Read a palette with size colors.
|
||||
//
|
||||
// This is a simple string, with 4 RGBA bytes per color.
|
||||
Palette GIFDecoder::ReadPalette(int palette_size)
|
||||
{
|
||||
Palette result;
|
||||
for(int i = 0; i < palette_size; ++i)
|
||||
{
|
||||
result.color[i].color[0] = stream.ReadByte(); // R
|
||||
result.color[i].color[1] = stream.ReadByte(); // G
|
||||
result.color[i].color[2] = stream.ReadByte(); // B
|
||||
result.color[i].color[3] = 0xFF;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool GIFDecoder::ReadPacket(string &packet)
|
||||
{
|
||||
uint8_t packet_size = stream.ReadByte();
|
||||
if(packet_size == 0)
|
||||
return false;
|
||||
|
||||
stream.ReadBytes(packet, packet_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GIFDecoder::ReadAllFrames(vector<SMXGif::SMXGifFrame> &frames)
|
||||
{
|
||||
string header;
|
||||
stream.ReadBytes(header, 6);
|
||||
|
||||
if(header != "GIF87a" && header != "GIF89a")
|
||||
throw GIFError();
|
||||
|
||||
GlobalGIFData global_data;
|
||||
|
||||
global_data.width = stream.ReadLE16();
|
||||
global_data.height = stream.ReadLE16();
|
||||
image.Init(global_data.width, global_data.height);
|
||||
|
||||
// Ignore the aspect ratio field. (Supporting pixel aspect ratios in a format
|
||||
// this rudimentary was almost ambitious of them...)
|
||||
uint8_t global_flags = stream.ReadByte();
|
||||
global_data.background_index = stream.ReadByte();
|
||||
|
||||
// Ignore the aspect ratio field. (Supporting pixel aspect ratios in a format
|
||||
// this rudimentary was almost ambitious of them...)
|
||||
stream.ReadByte();
|
||||
|
||||
// Decode global_flags.
|
||||
uint8_t global_palette_size = global_flags & 0x7;
|
||||
|
||||
global_data.have_global_palette = (global_flags >> 7) & 0x1;
|
||||
|
||||
|
||||
// If there's no global palette, leave it empty.
|
||||
if(global_data.have_global_palette)
|
||||
global_data.palette = ReadPalette(1 << (global_palette_size + 1));
|
||||
|
||||
frame = 0;
|
||||
|
||||
// Save a copy of global data, so we can restore it after each frame.
|
||||
GlobalGIFData saved_global_data = global_data;
|
||||
|
||||
// Decode all packets.
|
||||
while(1)
|
||||
{
|
||||
uint8_t packet_type = stream.ReadByte();
|
||||
if(packet_type == 0x21)
|
||||
{
|
||||
// Extension packet
|
||||
uint8_t extension_type = stream.ReadByte();
|
||||
|
||||
if(extension_type == 0xF9)
|
||||
{
|
||||
string packet;
|
||||
if(!ReadPacket(packet))
|
||||
throw GIFError();
|
||||
|
||||
DataStream packet_buf(packet);
|
||||
|
||||
// Graphics control extension
|
||||
uint8_t gce_flags = packet_buf.ReadByte();
|
||||
global_data.duration = packet_buf.ReadLE16();
|
||||
global_data.transparency_index = packet_buf.ReadByte();
|
||||
|
||||
global_data.use_transparency = bool(gce_flags & 1);
|
||||
global_data.disposal_method = (gce_flags >> 2) & 0xF;
|
||||
if(!global_data.use_transparency)
|
||||
global_data.transparency_index = -1;
|
||||
}
|
||||
|
||||
// Read any remaining packets in this extension packet.
|
||||
while(1)
|
||||
{
|
||||
string packet;
|
||||
if(!ReadPacket(packet))
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(packet_type == 0x2C)
|
||||
{
|
||||
// Image data
|
||||
SMXGif::GIFImage frame_image;
|
||||
DecodeImage(global_data, frame_image);
|
||||
|
||||
SMXGif::SMXGifFrame gif_frame;
|
||||
gif_frame.width = global_data.width;
|
||||
gif_frame.height = global_data.height;
|
||||
gif_frame.milliseconds = global_data.duration * 10;
|
||||
gif_frame.frame = frame_image;
|
||||
frames.push_back(gif_frame);
|
||||
|
||||
frame++;
|
||||
|
||||
// Reset GCE (frame-specific) data.
|
||||
global_data = saved_global_data;
|
||||
}
|
||||
else if(packet_type == 0x3B)
|
||||
{
|
||||
// EOF
|
||||
return;
|
||||
}
|
||||
else
|
||||
throw GIFError();
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a single GIF image into out, leaving this->image ready for
|
||||
// the next frame (with this frame's dispose applied).
|
||||
void GIFDecoder::DecodeImage(GlobalGIFData global_data, SMXGif::GIFImage &out)
|
||||
{
|
||||
uint16_t block_left = stream.ReadLE16();
|
||||
uint16_t block_top = stream.ReadLE16();
|
||||
uint16_t block_width = stream.ReadLE16();
|
||||
uint16_t block_height = stream.ReadLE16();
|
||||
uint8_t local_flags = stream.ReadByte();
|
||||
|
||||
// area = (block_left, block_top, block_left + block_width, block_top + block_height)
|
||||
// Extract flags:
|
||||
uint8_t have_local_palette = (local_flags >> 7) & 1;
|
||||
// bool interlaced = (local_flags >> 6) & 1;
|
||||
uint8_t local_palette_size = (local_flags >> 0) & 0x7;
|
||||
// print 'Interlaced:', interlaced
|
||||
|
||||
// We don't support interlaced GIFs right now.
|
||||
// assert interlaced == 0
|
||||
|
||||
// If this frame has a local palette, use it. Otherwise, use the global palette.
|
||||
Palette active_palette = global_data.palette;
|
||||
if(have_local_palette)
|
||||
active_palette = ReadPalette(1 << (local_palette_size + 1));
|
||||
|
||||
if(!global_data.have_global_palette && !have_local_palette)
|
||||
{
|
||||
// We have no palette. This is an invalid file.
|
||||
throw GIFError();
|
||||
}
|
||||
|
||||
if(frame == 0)
|
||||
{
|
||||
// On the first frame, clear the buffer. If we have a transparency index,
|
||||
// clear to transparent. Otherwise, clear to the background color.
|
||||
if(global_data.transparency_index != -1)
|
||||
image.Clear(SMXGif::Color(0,0,0,0));
|
||||
else
|
||||
image.Clear(active_palette.color[global_data.background_index]);
|
||||
}
|
||||
|
||||
// Decode the compressed image data.
|
||||
LWZDecoder decoder(stream);
|
||||
string decompressed_data = decoder.DecodeImage();
|
||||
|
||||
if(decompressed_data.size() < block_width*block_height)
|
||||
throw GIFError();
|
||||
|
||||
// Save the region to restore after decoding.
|
||||
SMXGif::GIFImage dispose;
|
||||
if(global_data.disposal_method <= 1)
|
||||
{
|
||||
// No disposal.
|
||||
}
|
||||
else if(global_data.disposal_method == 2)
|
||||
{
|
||||
// Clear the region to a background color afterwards.
|
||||
dispose.Init(block_width, block_height);
|
||||
|
||||
if(global_data.transparency_index != -1)
|
||||
dispose.Clear(SMXGif::Color(0,0,0,0));
|
||||
else
|
||||
{
|
||||
uint8_t palette_idx = global_data.background_index;
|
||||
dispose.Clear(active_palette.color[palette_idx]);
|
||||
}
|
||||
|
||||
}
|
||||
else if(global_data.disposal_method == 3)
|
||||
{
|
||||
// Restore the previous frame afterwards.
|
||||
image.CropImage(dispose, block_left, block_top, block_width, block_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown disposal method
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
for(int y = block_top; y < block_top + block_height; ++y)
|
||||
{
|
||||
for(int x = block_left; x < block_left + block_width; ++x)
|
||||
{
|
||||
uint8_t palette_idx = decompressed_data[pos];
|
||||
pos++;
|
||||
|
||||
if(palette_idx == global_data.transparency_index)
|
||||
{
|
||||
// If this pixel is transparent, leave the existing color in place.
|
||||
}
|
||||
else
|
||||
{
|
||||
image.get(x,y) = active_palette.color[palette_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the image before we run dispose.
|
||||
out = image;
|
||||
|
||||
// Restore the dispose area.
|
||||
if(dispose.width != 0)
|
||||
image.Blit(dispose, block_left, block_top, block_width, block_height);
|
||||
}
|
||||
|
||||
bool SMXGif::DecodeGIF(string buf, vector<SMXGif::SMXGifFrame> &frames)
|
||||
{
|
||||
DataStream stream(buf);
|
||||
GIFDecoder gif(stream);
|
||||
try {
|
||||
gif.ReadAllFrames(frames);
|
||||
} catch(GIFError &) {
|
||||
// We don't return error strings for this, just success or failure.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
70
sdk/Windows/SMXGif.h
Normal file
70
sdk/Windows/SMXGif.h
Normal file
@ -0,0 +1,70 @@
|
||||
#ifndef SMXGif_h
|
||||
#define SMXGif_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// This is a simple internal GIF decoder. It's only meant to be used by
|
||||
// SMXConfig.
|
||||
namespace SMXGif
|
||||
{
|
||||
struct Color
|
||||
{
|
||||
uint8_t color[4];
|
||||
Color()
|
||||
{
|
||||
memset(color, 0, sizeof(color));
|
||||
}
|
||||
|
||||
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
{
|
||||
color[0] = r;
|
||||
color[1] = g;
|
||||
color[2] = b;
|
||||
color[3] = a;
|
||||
}
|
||||
bool operator==(const Color &rhs) const
|
||||
{
|
||||
return !memcmp(color, rhs.color, sizeof(*color));
|
||||
}
|
||||
};
|
||||
|
||||
struct GIFImage
|
||||
{
|
||||
int width = 0, height = 0;
|
||||
void Init(int width, int height);
|
||||
|
||||
Color get(int x, int y) const { return image[y*width+x]; }
|
||||
Color &get(int x, int y) { return image[y*width+x]; }
|
||||
|
||||
// Clear to a solid color.
|
||||
void Clear(const Color &color);
|
||||
|
||||
// Copy a rectangle from this image into dst.
|
||||
void CropImage(GIFImage &dst, int crop_left, int crop_top, int crop_width, int crop_height) const;
|
||||
|
||||
// Copy src into a rectangle in this image.
|
||||
void Blit(GIFImage &src, int dst_left, int dst_top, int dst_width, int dst_height);
|
||||
|
||||
private:
|
||||
std::vector<Color> image;
|
||||
};
|
||||
|
||||
struct SMXGifFrame
|
||||
{
|
||||
int width = 0, height = 0;
|
||||
|
||||
// GIF images have a delay in 10ms units. We use 1ms for clarity.
|
||||
int milliseconds = 0;
|
||||
|
||||
GIFImage frame;
|
||||
};
|
||||
|
||||
// Decode a GIF into a list of frames.
|
||||
bool DecodeGIF(std::string buf, std::vector<SMXGifFrame> &frames);
|
||||
}
|
||||
|
||||
void gif_test();
|
||||
|
||||
#endif
|
@ -241,24 +241,11 @@ void SMX::SMXManager::ThreadMain()
|
||||
// that we don't send the second lights commands, since that may re-disable auto lights.
|
||||
// - If we have two pads, the lights update is for both pads and we'll send both commands
|
||||
// for both pads at the same time, so both pads update lights simultaneously.
|
||||
void SMX::SMXManager::SetLights(const string &sLightData)
|
||||
void SMX::SMXManager::SetLights(const string sPanelLights[2])
|
||||
{
|
||||
g_Lock.AssertNotLockedByCurrentThread();
|
||||
LockMutex L(g_Lock);
|
||||
|
||||
// Sanity check the lights data. It should have 18*16*3 bytes of data: RGB for each of 4x4
|
||||
// LEDs on 18 panels.
|
||||
if(sLightData.size() != 2*3*3*16*3)
|
||||
{
|
||||
Log(ssprintf("SetLights: Lights data should be %i bytes, received %i", 2*3*3*16*3, sLightData.size()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Split the lights data into P1 and P2.
|
||||
string sPanelLights[2];
|
||||
sPanelLights[0] = sLightData.substr(0, 9*16*3);
|
||||
sPanelLights[1] = sLightData.substr(9*16*3);
|
||||
|
||||
// Separate top and bottom lights commands.
|
||||
//
|
||||
// sPanelLights[iPad] is
|
||||
@ -281,44 +268,46 @@ void SMX::SMXManager::SetLights(const string &sLightData)
|
||||
// Set sLightsCommand[iPad][0] to include 0123 4567, and [1] to 89AB CDEF.
|
||||
string sLightCommands[2][2]; // sLightCommands[command][pad]
|
||||
|
||||
auto addByte = [&sLightCommands](int iPanel, int iByte, uint8_t iColor) {
|
||||
// If iPanel is 0-8, this is for pad 0. For 9-17, it's for pad 1.
|
||||
// If the color byte within the panel is in the top half, it's the first
|
||||
// command, otherwise it's the second command.
|
||||
int iPad = iPanel < 9? 0:1;
|
||||
int iCommandIndex = iByte < 4*2*3? 0:1;
|
||||
sLightCommands[iCommandIndex][iPad].append(1, iColor);
|
||||
};
|
||||
|
||||
// Read the linearly arranged color data we've been given and split it into top and
|
||||
// bottom commands for each pad.
|
||||
for(int iPad = 0; iPad < 2; ++iPad)
|
||||
{
|
||||
// If there's no data for this pad, leave the command empty.
|
||||
const string &sLightsDataForPad = sPanelLights[iPad];
|
||||
if(sLightsDataForPad.empty())
|
||||
continue;
|
||||
|
||||
// Sanity check the lights data. It should have 9*4*4*3 bytes of data: RGB for each of 4x4
|
||||
// LEDs on 9 panels.
|
||||
if(sPanelLights[iPad].size() != 9*16*3)
|
||||
{
|
||||
Log(ssprintf("SetLights: Lights data should be %i bytes, received %i", 3*3*16*3, sPanelLights[iPad].size()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// The 2 command sends the top 4x2 lights, and 3 sends the bottom 4x2.
|
||||
sLightCommands[0][iPad] = "2";
|
||||
sLightCommands[1][iPad] = "3";
|
||||
int iNextInputByte = 0;
|
||||
for(int iPanel = 0; iPanel < 18; ++iPanel)
|
||||
for(int iPanel = 0; iPanel < 9; ++iPanel)
|
||||
{
|
||||
for(int iByte = 0; iByte < 4*4*3; ++iByte)
|
||||
{
|
||||
uint8_t iColor = sLightData[iNextInputByte++];
|
||||
addByte(iPanel, iByte, iColor);
|
||||
}
|
||||
}
|
||||
|
||||
for(int iPad = 0; iPad < 2; ++iPad)
|
||||
{
|
||||
for(int iCommand = 0; iCommand < 2; ++iCommand)
|
||||
{
|
||||
string &sCommand = sLightCommands[iCommand][iPad];
|
||||
uint8_t iColor = sLightsDataForPad[iNextInputByte++];
|
||||
|
||||
// Apply color scaling. Values over about 170 don't make the LEDs any brighter, so this
|
||||
// gives better contrast and draws less power.
|
||||
for(char &c: sCommand)
|
||||
c = char(uint8_t(c) * 0.6666f);
|
||||
iColor = uint8_t(iColor * 0.6666f);
|
||||
|
||||
// Add the command byte.
|
||||
sCommand.insert(sCommand.begin(), 1, iCommand == 0? '2':'3');
|
||||
sCommand.push_back('\n');
|
||||
int iCommandIndex = iByte < 4*2*3? 0:1;
|
||||
sLightCommands[iCommandIndex][iPad].append(1, iColor);
|
||||
}
|
||||
}
|
||||
|
||||
sLightCommands[0][iPad].push_back('\n');
|
||||
sLightCommands[1][iPad].push_back('\n');
|
||||
}
|
||||
|
||||
// Each update adds two entries to m_aPendingCommands, one for the top half and one
|
||||
// for the lower half.
|
||||
//
|
||||
@ -356,13 +345,18 @@ void SMX::SMXManager::SetLights(const string &sLightData)
|
||||
|
||||
// Set the pad commands.
|
||||
PendingCommand *pPendingCommands[2];
|
||||
pPendingCommands[0] = &m_aPendingCommands[m_aPendingCommands.size()-2];
|
||||
pPendingCommands[1] = &m_aPendingCommands[m_aPendingCommands.size()-1];
|
||||
pPendingCommands[0] = &m_aPendingCommands[m_aPendingCommands.size()-2]; // 2
|
||||
pPendingCommands[1] = &m_aPendingCommands[m_aPendingCommands.size()-1]; // 3
|
||||
|
||||
pPendingCommands[0]->sPadCommand[0] = sLightCommands[0][0];
|
||||
pPendingCommands[0]->sPadCommand[1] = sLightCommands[0][1];
|
||||
pPendingCommands[1]->sPadCommand[0] = sLightCommands[1][0];
|
||||
pPendingCommands[1]->sPadCommand[1] = sLightCommands[1][1];
|
||||
for(int iPad = 0; iPad < 2; ++iPad)
|
||||
{
|
||||
// If the command for this pad is empty, leave any existing pad command alone.
|
||||
if(sLightCommands[0][iPad].empty())
|
||||
continue;
|
||||
|
||||
pPendingCommands[0]->sPadCommand[iPad] = sLightCommands[0][iPad];
|
||||
pPendingCommands[1]->sPadCommand[iPad] = sLightCommands[1][iPad];
|
||||
}
|
||||
}
|
||||
|
||||
void SMX::SMXManager::ReenableAutoLights()
|
||||
@ -395,7 +389,10 @@ void SMX::SMXManager::SendLightUpdates()
|
||||
// Send the lights command for each pad. If either pad isn't connected, this won't do
|
||||
// anything.
|
||||
for(int iPad = 0; iPad < 2; ++iPad)
|
||||
{
|
||||
if(!command.sPadCommand[iPad].empty())
|
||||
m_pDevices[iPad]->SendCommandLocked(command.sPadCommand[iPad]);
|
||||
}
|
||||
|
||||
// Remove the command we've sent.
|
||||
m_aPendingCommands.erase(m_aPendingCommands.begin(), m_aPendingCommands.begin()+1);
|
||||
|
@ -43,7 +43,7 @@ public:
|
||||
|
||||
void Shutdown();
|
||||
shared_ptr<SMXDevice> GetDevice(int pad);
|
||||
void SetLights(const string &sLightData);
|
||||
void SetLights(const string sLights[2]);
|
||||
void ReenableAutoLights();
|
||||
|
||||
private:
|
||||
|
Loading…
x
Reference in New Issue
Block a user