Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 1 | /* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. |
DRC | cd2c5d4 | 2011-08-11 11:18:34 +0000 | [diff] [blame] | 2 | * Copyright (C) 2011 D. R. Commander. All Rights Reserved. |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 3 | * Copyright 2014 Pierre Ossman for Cendio AB |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 4 | * |
| 5 | * This is free software; you can redistribute it and/or modify |
| 6 | * it under the terms of the GNU General Public License as published by |
| 7 | * the Free Software Foundation; either version 2 of the License, or |
| 8 | * (at your option) any later version. |
| 9 | * |
| 10 | * This software is distributed in the hope that it will be useful, |
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | * GNU General Public License for more details. |
| 14 | * |
| 15 | * You should have received a copy of the GNU General Public License |
| 16 | * along with this software; if not, write to the Free Software |
| 17 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
| 18 | * USA. |
| 19 | */ |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 20 | #include <assert.h> |
| 21 | |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 22 | #include <rdr/OutStream.h> |
Pierre Ossman | 7638e9c | 2014-01-16 13:12:40 +0100 | [diff] [blame] | 23 | #include <rfb/PixelBuffer.h> |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 24 | #include <rfb/Palette.h> |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 25 | #include <rfb/encodings.h> |
| 26 | #include <rfb/ConnParams.h> |
Pierre Ossman | 668468b | 2014-01-31 12:37:32 +0100 | [diff] [blame] | 27 | #include <rfb/SConnection.h> |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 28 | #include <rfb/TightEncoder.h> |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 29 | #include <rfb/TightConstants.h> |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 30 | |
| 31 | using namespace rfb; |
| 32 | |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 33 | struct TightConf { |
| 34 | int idxZlibLevel, monoZlibLevel, rawZlibLevel; |
| 35 | }; |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 36 | |
| 37 | // |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 38 | // Compression level stuff. The following array contains zlib |
| 39 | // settings for each of 10 compression levels (0..9). |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 40 | // |
DRC | cd2c5d4 | 2011-08-11 11:18:34 +0000 | [diff] [blame] | 41 | // NOTE: The parameters used in this encoder are the result of painstaking |
| 42 | // research by The VirtualGL Project using RFB session captures from a variety |
| 43 | // of both 2D and 3D applications. See http://www.VirtualGL.org for the full |
| 44 | // reports. |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 45 | |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 46 | static const TightConf conf[10] = { |
| 47 | { 0, 0, 0 }, // 0 |
| 48 | { 1, 1, 1 }, // 1 |
| 49 | { 3, 3, 2 }, // 2 |
| 50 | { 5, 5, 2 }, // 3 |
| 51 | { 6, 7, 3 }, // 4 |
| 52 | { 7, 8, 4 }, // 5 |
| 53 | { 7, 8, 5 }, // 6 |
| 54 | { 8, 9, 6 }, // 7 |
| 55 | { 9, 9, 7 }, // 8 |
| 56 | { 9, 9, 9 } // 9 |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 57 | }; |
Pierre Ossman | 701ad68 | 2011-11-20 15:39:17 +0000 | [diff] [blame] | 58 | |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 59 | TightEncoder::TightEncoder(SConnection* conn) : |
| 60 | Encoder(conn, encodingTight, EncoderPlain, 256) |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 61 | { |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 62 | setCompressLevel(-1); |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 63 | } |
| 64 | |
| 65 | TightEncoder::~TightEncoder() |
| 66 | { |
| 67 | } |
| 68 | |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 69 | bool TightEncoder::isSupported() |
| 70 | { |
| 71 | return conn->cp.supportsEncoding(encodingTight); |
| 72 | } |
| 73 | |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 74 | void TightEncoder::setCompressLevel(int level) |
| 75 | { |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 76 | if (level < 0 || level > 9) |
| 77 | level = 2; |
| 78 | |
| 79 | idxZlibLevel = conf[level].idxZlibLevel; |
Michal Srb | 2a2a7bb | 2016-05-06 14:48:36 +0300 | [diff] [blame] | 80 | monoZlibLevel = conf[level].monoZlibLevel; |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 81 | rawZlibLevel = conf[level].rawZlibLevel; |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 82 | } |
| 83 | |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 84 | void TightEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 85 | { |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 86 | switch (palette.size()) { |
| 87 | case 0: |
| 88 | writeFullColourRect(pb, palette); |
| 89 | break; |
| 90 | case 1: |
| 91 | Encoder::writeSolidRect(pb, palette); |
| 92 | break; |
| 93 | case 2: |
| 94 | writeMonoRect(pb, palette); |
| 95 | break; |
DRC | cd2c5d4 | 2011-08-11 11:18:34 +0000 | [diff] [blame] | 96 | default: |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 97 | writeIndexedRect(pb, palette); |
DRC | cd2c5d4 | 2011-08-11 11:18:34 +0000 | [diff] [blame] | 98 | } |
| 99 | } |
| 100 | |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 101 | void TightEncoder::writeSolidRect(int width, int height, |
| 102 | const PixelFormat& pf, |
| 103 | const rdr::U8* colour) |
DRC | cd2c5d4 | 2011-08-11 11:18:34 +0000 | [diff] [blame] | 104 | { |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 105 | rdr::OutStream* os; |
DRC | cd2c5d4 | 2011-08-11 11:18:34 +0000 | [diff] [blame] | 106 | |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 107 | os = conn->getOutStream(); |
DRC | cd2c5d4 | 2011-08-11 11:18:34 +0000 | [diff] [blame] | 108 | |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 109 | os->writeU8(tightFill << 4); |
| 110 | writePixels(colour, pf, 1, os); |
DRC | cd2c5d4 | 2011-08-11 11:18:34 +0000 | [diff] [blame] | 111 | } |
| 112 | |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 113 | void TightEncoder::writeMonoRect(const PixelBuffer* pb, const Palette& palette) |
DRC | cd2c5d4 | 2011-08-11 11:18:34 +0000 | [diff] [blame] | 114 | { |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 115 | const rdr::U8* buffer; |
| 116 | int stride; |
DRC | cd2c5d4 | 2011-08-11 11:18:34 +0000 | [diff] [blame] | 117 | |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 118 | buffer = pb->getBuffer(pb->getRect(), &stride); |
DRC | cd2c5d4 | 2011-08-11 11:18:34 +0000 | [diff] [blame] | 119 | |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 120 | switch (pb->getPF().bpp) { |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 121 | case 32: |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 122 | writeMonoRect(pb->width(), pb->height(), (rdr::U32*)buffer, stride, |
| 123 | pb->getPF(), palette); |
| 124 | break; |
| 125 | case 16: |
| 126 | writeMonoRect(pb->width(), pb->height(), (rdr::U16*)buffer, stride, |
| 127 | pb->getPF(), palette); |
| 128 | break; |
| 129 | default: |
| 130 | writeMonoRect(pb->width(), pb->height(), (rdr::U8*)buffer, stride, |
| 131 | pb->getPF(), palette); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | void TightEncoder::writeIndexedRect(const PixelBuffer* pb, const Palette& palette) |
| 136 | { |
| 137 | const rdr::U8* buffer; |
| 138 | int stride; |
| 139 | |
| 140 | buffer = pb->getBuffer(pb->getRect(), &stride); |
| 141 | |
| 142 | switch (pb->getPF().bpp) { |
| 143 | case 32: |
| 144 | writeIndexedRect(pb->width(), pb->height(), (rdr::U32*)buffer, stride, |
| 145 | pb->getPF(), palette); |
| 146 | break; |
| 147 | case 16: |
| 148 | writeIndexedRect(pb->width(), pb->height(), (rdr::U16*)buffer, stride, |
| 149 | pb->getPF(), palette); |
| 150 | break; |
| 151 | default: |
| 152 | // It's more efficient to just do raw pixels |
| 153 | writeFullColourRect(pb, palette); |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | void TightEncoder::writeFullColourRect(const PixelBuffer* pb, const Palette& palette) |
| 158 | { |
| 159 | const int streamId = 0; |
| 160 | |
| 161 | rdr::OutStream* os; |
| 162 | rdr::OutStream* zos; |
| 163 | int length; |
| 164 | |
| 165 | const rdr::U8* buffer; |
Pierre Ossman | eb95532 | 2015-03-03 16:10:53 +0100 | [diff] [blame] | 166 | int stride, h; |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 167 | |
| 168 | os = conn->getOutStream(); |
| 169 | |
| 170 | os->writeU8(streamId << 4); |
| 171 | |
| 172 | // Set up compression |
| 173 | if ((pb->getPF().bpp != 32) || !pb->getPF().is888()) |
| 174 | length = pb->getRect().area() * pb->getPF().bpp/8; |
| 175 | else |
| 176 | length = pb->getRect().area() * 3; |
| 177 | |
| 178 | zos = getZlibOutStream(streamId, rawZlibLevel, length); |
| 179 | |
| 180 | // And then just dump all the raw pixels |
| 181 | buffer = pb->getBuffer(pb->getRect(), &stride); |
| 182 | h = pb->height(); |
| 183 | |
| 184 | while (h--) { |
| 185 | writePixels(buffer, pb->getPF(), pb->width(), zos); |
| 186 | buffer += stride * pb->getPF().bpp/8; |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 187 | } |
| 188 | |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 189 | // Finish the zlib stream |
| 190 | flushZlibOutStream(zos); |
| 191 | } |
| 192 | |
| 193 | void TightEncoder::writePixels(const rdr::U8* buffer, const PixelFormat& pf, |
| 194 | unsigned int count, rdr::OutStream* os) |
| 195 | { |
| 196 | rdr::U8 rgb[2048]; |
| 197 | |
| 198 | if ((pf.bpp != 32) || !pf.is888()) { |
| 199 | os->writeBytes(buffer, count * pf.bpp/8); |
| 200 | return; |
| 201 | } |
| 202 | |
| 203 | while (count) { |
Pierre Ossman | 5c23b9e | 2015-03-03 16:26:03 +0100 | [diff] [blame] | 204 | unsigned int iter_count; |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 205 | |
| 206 | iter_count = sizeof(rgb)/3; |
| 207 | if (iter_count > count) |
| 208 | iter_count = count; |
| 209 | |
| 210 | pf.rgbFromBuffer(rgb, buffer, iter_count); |
| 211 | os->writeBytes(rgb, iter_count * 3); |
| 212 | |
| 213 | buffer += iter_count * pf.bpp/8; |
| 214 | count -= iter_count; |
| 215 | } |
Constantin Kaplinsky | a2adc8d | 2006-05-25 05:01:55 +0000 | [diff] [blame] | 216 | } |
Pierre Ossman | 7b5c069 | 2014-03-17 14:35:51 +0100 | [diff] [blame] | 217 | |
| 218 | void TightEncoder::writeCompact(rdr::OutStream* os, rdr::U32 value) |
| 219 | { |
| 220 | rdr::U8 b; |
| 221 | b = value & 0x7F; |
| 222 | if (value <= 0x7F) { |
| 223 | os->writeU8(b); |
| 224 | } else { |
| 225 | os->writeU8(b | 0x80); |
| 226 | b = value >> 7 & 0x7F; |
| 227 | if (value <= 0x3FFF) { |
| 228 | os->writeU8(b); |
| 229 | } else { |
| 230 | os->writeU8(b | 0x80); |
| 231 | os->writeU8(value >> 14 & 0xFF); |
| 232 | } |
| 233 | } |
| 234 | } |
Pierre Ossman | c039726 | 2014-03-14 15:59:46 +0100 | [diff] [blame] | 235 | |
| 236 | rdr::OutStream* TightEncoder::getZlibOutStream(int streamId, int level, size_t length) |
| 237 | { |
| 238 | // Minimum amount of data to be compressed. This value should not be |
| 239 | // changed, doing so will break compatibility with existing clients. |
| 240 | if (length < 12) |
| 241 | return conn->getOutStream(); |
| 242 | |
| 243 | assert(streamId >= 0); |
| 244 | assert(streamId < 4); |
| 245 | |
| 246 | zlibStreams[streamId].setUnderlying(&memStream); |
| 247 | zlibStreams[streamId].setCompressionLevel(level); |
| 248 | |
| 249 | return &zlibStreams[streamId]; |
| 250 | } |
| 251 | |
| 252 | void TightEncoder::flushZlibOutStream(rdr::OutStream* os_) |
| 253 | { |
| 254 | rdr::OutStream* os; |
| 255 | rdr::ZlibOutStream* zos; |
| 256 | |
| 257 | zos = dynamic_cast<rdr::ZlibOutStream*>(os_); |
| 258 | if (zos == NULL) |
| 259 | return; |
| 260 | |
| 261 | zos->flush(); |
| 262 | zos->setUnderlying(NULL); |
| 263 | |
| 264 | os = conn->getOutStream(); |
| 265 | |
| 266 | writeCompact(os, memStream.length()); |
| 267 | os->writeBytes(memStream.data(), memStream.length()); |
| 268 | memStream.clear(); |
| 269 | } |
| 270 | |
| 271 | // |
| 272 | // Including BPP-dependent implementation of the encoder. |
| 273 | // |
| 274 | |
| 275 | #define BPP 8 |
| 276 | #include <rfb/TightEncoderBPP.cxx> |
| 277 | #undef BPP |
| 278 | #define BPP 16 |
| 279 | #include <rfb/TightEncoderBPP.cxx> |
| 280 | #undef BPP |
| 281 | #define BPP 32 |
| 282 | #include <rfb/TightEncoderBPP.cxx> |
| 283 | #undef BPP |