- Improved efficiency and readability of the RfbBitmap configuration logic. Refactored redundant code blocks for different pixel format (bpp) configurations into a single, streamlined method. This change enhances maintainability and clarity of the bitmap configuration process.
- Added cursor pseudo encoding support. - Added Windows support for cursor image capturing in `get_cursor_image` method. Implemented Windows-specific logic using the `win32gui`, `win32ui`, and related libraries to capture the current cursor image, enhancing the cross-platform capability of the application. - Fixed issues in `get_bitmap` method for handling different bpp formats. Specifically addressed the processing logic for 16 bpp (BGR565) format, ensuring that the image conversion and rendering are handled correctly for VNC clients expecting this format. - Added initial Tight encoding support. - Updated the `send_image` method in the Tight encoding class to correctly handle JPEG and ZLIB compression. This includes proper signaling to the client about the type of compression used (JPEG or ZLIB) and ensuring that the data is formatted and sent according to the Tight encoding specifications. - Added checks and conversions in `send_image` to handle different image modes (like RGBX and RGBA) and convert them to the appropriate format (RGB) before compression and transmission. - Implemented a more robust and accurate method for determining when to use JPEG compression in Tight encoding based on the unique color count and image characteristics. These updates significantly improve the functionality, stability, and compatibility of the VNC server, particularly in handling different pixel formats and encoding methods.
This commit is contained in:
101
lib/encodings/tight.py
Normal file
101
lib/encodings/tight.py
Normal file
@@ -0,0 +1,101 @@
|
||||
from . import common
|
||||
from lib import log
|
||||
from struct import *
|
||||
import zlib
|
||||
from io import BytesIO
|
||||
import numpy as np
|
||||
|
||||
class Encoding:
|
||||
name = 'tight'
|
||||
id = 7
|
||||
description = 'Tight VNC encoding'
|
||||
enabled = True
|
||||
|
||||
def __init__(self):
|
||||
self.compress_obj = zlib.compressobj(
|
||||
zlib.Z_DEFAULT_COMPRESSION,
|
||||
zlib.DEFLATED,
|
||||
zlib.MAX_WBITS,
|
||||
zlib.DEF_MEM_LEVEL,
|
||||
zlib.Z_DEFAULT_STRATEGY
|
||||
)
|
||||
self._last_compression_was_jpeg = False
|
||||
|
||||
def send_image(self, x, y, w, h, image):
|
||||
sendbuff = bytearray()
|
||||
|
||||
if image.mode == 'RGBX' or image.mode == 'RGBA':
|
||||
image = image.convert('RGB')
|
||||
|
||||
rectangles = 1
|
||||
sendbuff.extend(pack("!BxH", 0, rectangles)) # FramebufferUpdate
|
||||
sendbuff.extend(pack("!HHHH", x, y, w, h))
|
||||
sendbuff.extend(pack(">i", self.id))
|
||||
|
||||
if self._should_use_jpeg(image, 64):
|
||||
self._last_compression_was_jpeg = True
|
||||
compressed_data = self._compress_image_jpeg(image)
|
||||
sendbuff.append(0x90) # 0x90 = 10010000 = JPEG subencoding
|
||||
else:
|
||||
compressed_data = self._compress_image_zlib(image)
|
||||
sendbuff.append(0) # control byte
|
||||
|
||||
# content lenght
|
||||
sendbuff.extend(self._send_compact_length(len(compressed_data)))
|
||||
|
||||
# Tight data
|
||||
sendbuff.extend(compressed_data)
|
||||
|
||||
return sendbuff
|
||||
|
||||
def _send_compact_length(self, length):
|
||||
sendbuff = bytearray()
|
||||
while True:
|
||||
# Toma los 7 bits más bajos del tamaño
|
||||
byte = length & 0x7F
|
||||
length >>= 7
|
||||
# Si aún hay más datos, establece el bit alto y continúa
|
||||
if length > 0:
|
||||
byte |= 0x80
|
||||
sendbuff.append(byte)
|
||||
if length == 0:
|
||||
break
|
||||
return sendbuff
|
||||
|
||||
def _should_use_jpeg(self, image, threshold=256):
|
||||
if image.mode == 'P':
|
||||
return False
|
||||
|
||||
if image.mode == 'RGB':
|
||||
width, height = image.size
|
||||
sample_size = min(width * height, 1000)
|
||||
sample = np.array(image).reshape(-1, 3)[:sample_size]
|
||||
unique_colors = np.unique(sample, axis=0)
|
||||
return len(unique_colors) > threshold
|
||||
|
||||
return False
|
||||
|
||||
def _compress_image_jpeg(self, image, quality=75):
|
||||
buffer = BytesIO()
|
||||
image.save(buffer, format="JPEG", quality=quality)
|
||||
jpeg_data = buffer.getvalue()
|
||||
buffer.close()
|
||||
return jpeg_data
|
||||
|
||||
def _compress_image_zlib(self, image):
|
||||
if self.compress_obj is None or self._last_compression_was_jpeg:
|
||||
self.compress_obj = zlib.compressobj(
|
||||
zlib.Z_DEFAULT_COMPRESSION,
|
||||
zlib.DEFLATED,
|
||||
-zlib.MAX_WBITS, # El negativo omite la inclusión de un encabezado zlib.
|
||||
zlib.DEF_MEM_LEVEL,
|
||||
zlib.Z_DEFAULT_STRATEGY
|
||||
)
|
||||
self._last_compression_was_jpeg = False
|
||||
|
||||
zlib_data = self.compress_obj.compress(image.tobytes()) + self.compress_obj.flush(zlib.Z_SYNC_FLUSH)
|
||||
return zlib_data
|
||||
|
||||
|
||||
common.encodings[common.ENCODINGS.tight] = Encoding
|
||||
log.debug("Loaded encoding: %s (%s)" % (__name__, Encoding.id))
|
||||
Reference in New Issue
Block a user