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