#!/usr/bin/python3 # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (C) 2018, Google Inc. # # Author: Laurent Pinchart # # checkstyle.py - A patch style checker script based on astyle or clang-format # # TODO: # # - Support other formatting tools and checkers (cppcheck, cpplint, kwstyle, ...) # - Split large hunks to minimize context noise # - Improve style issues counting # import argparse import difflib import fnmatch import os.path import re import shutil import subprocess import sys astyle_options = ( '-n', '--style=linux', '--indent=force-tab=8', '--attach-namespaces', '--attach-extern-c', '--pad-oper', '--align-pointer=name', '--align-reference=name', '--keep-one-line-blocks', '--max-code-length=120' ) dependencies = { 'astyle': False, 'clang-format': False, 'git': True, } # ------------------------------------------------------------------------------ # Colour terminal handling # class Colours: Default = 0 Black = 0 Red = 31 Green = 32 Yellow = 33 Blue = 34 Magenta = 35 Cyan = 36 LightGrey = 37 DarkGrey = 90 LightRed = 91 LightGreen = 92 Lightyellow = 93 LightBlue = 94 LightMagenta = 95 LightCyan = 96 White = 97 @staticmethod def fg(colour): if sys.stdout.isatty(): return '\033[%um' % colour else: return '' @staticmethod def bg(colour): if sys.stdout.isatty(): return '\033[%um' % (colour + 10) else: return '' @staticmethod def reset(): if sys.stdout.isatty(): return '\033[0m' else: return '' # ------------------------------------------------------------------------------ # Diff parsing, handling and printing # class DiffHunkSide(object): """A side of a diff hunk, recording line numbers""" def __init__(self, start): self.start = start self.touched = [] self.untouched = [] def __len__(self): return len(self.touched) + len(self.untouched) class DiffHunk(object): diff_header_regex = re.compile(r'@@ -([0-9]+),?([0-9]+)? \+([0-9]+),?([0-9]+)? @@') def __init__(self, line): match = DiffHunk.diff_header_regex.match(line) if not match: raise RuntimeError("Malformed diff hunk header '%s'" % line) self.__from_line = int(match.group(1)) self.__to_line = int(match.group(3)) self.__from = DiffHunkSide(self.__from_line) self.__to = DiffHunkSide(self.__to_line) self.lines = [] def __repr__(self): s = '%s@@ -%u,%u +%u,%u @@\n' % \ (Colours.fg(Colours.Cyan), self.__from.start, len(self.__from), self.__to.start, len(self.__to)) for line in self.lines: if line[0] == '-': s += Colours.fg(Colours.Red) elif line[0] == '+': s += Colours.fg(Colours.Green) if line[0] == '-': spaces = 0 for i in range(len(line)): if line[-i-1].isspace(): spaces += 1 else: break spaces = len(line) - spaces line = line[0:spaces] + Colours.bg(Colours.Red) + line[spaces:] s += line s += Colours.reset() s += '\n' return s[:-1] def append(self, line): if line[0] == ' ': self.__from.untouched.append(self.__from_line) self.__from_line += 1 self.__to.untouched.append(self.__to_line) self.__to_line += 1 elif line[0] == '-': self.__from.touched.append(self.__from_line) self.__from_line += 1 elif line[0] == '+': self.__to.touched.append(self.__to_line) self.__to_line += 1 self.lines.append(line.rstrip('\n')) def intersects(self, lines): for line in lines: if line in self.__from.touched: return True return False def side(self, side): if side == 'from': return self.__from else: return self.__to def parse_diff(diff): hunks = [] hunk = None for line in diff: if line.startswith('@@'): if hunk: hunks.append(hunk) hunk = DiffHunk(line) elif hunk is not None: hunk.append(line) if hunk: hunks.append(hunk) return hunks # ------------------------------------------------------------------------------ # Style Checkers # _style_checkers = [] class StyleCheckerRegistry(type): def __new__(cls, clsname, bases, attrs): newclass = super(StyleCheckerRegistry, cls).__new__(cls, clsname, bases, attrs) if clsname != 'StyleChecker': _style_checkers.append(newclass) return newclass class StyleChecker(metaclass=StyleCheckerRegistry): def __init__(self): pass # # Class methods # @classmethod def checkers(cls, filename): for checker in _style_checkers: if checker.supports(filename): yield checker @classmethod def supports(cls, filename): for pattern in cls.patterns: if fnmatch.fnmatch(os.path.basename(filename), pattern): return True return False @classmethod def all_patterns(cls): patterns = set() for checker in _style_checkers: patterns.update(checker.patterns) return patterns class StyleIssue(object): def __init__(self, line_number, line, msg): self.line_number = line_number self.line = line self.msg = msg class IncludeChecker(StyleChecker): patterns = ('*.cpp', '*.h') headers = ('assert', 'ctype', 'errno', 'fenv', 'float', 'inttypes', 'limits', 'locale', 'math', 'setjmp', 'signal', 'stdarg', 'stddef', 'stdint', 'stdio', 'stdlib', 'string', 'time', 'uchar', 'wchar', 'wctype') include_regex = re.compile('^#include ') def __init__(self, content): super().__init__() self.__content = content def check(self, line_numbers): issues = [] for line_number in line_numbers: line = self.__content[line_number - 1] match = IncludeChecker.include_regex.match(line) if not match: continue header = match.group(1) if header not in IncludeChecker.headers: continue issues.append(StyleIssue(line_number, line, 'C compatibility header <%s.h> is preferred' % header)) return issues class LogCategoryChecker(StyleChecker): log_regex = re.compile('\\bLOG\((Debug|Info|Warning|Error|Fatal)\)') patterns = ('*.cpp',) def __init__(self, content): super().__init__() self.__content = content def check(self, line_numbers): issues = [] for line_number in line_numbers: line = self.__content[line_number-1] if not LogCategoryChecker.log_regex.search(line): continue issues.append(StyleIssue(line_number, line, 'LOG() should use categories')) return issues class MesonChecker(StyleChecker): patterns = ('meson.build',) def __init__(self, content): super().__init__() self.__content = content def check(self, line_numbers): issues = [] for line_number in line_numbers: line = self.__content[line_number-1] if line.find('\t') != -1: issues.append(StyleIssue(line_number, line, 'meson.build should use spaces for indentation')) return issues class Pep8Checker(StyleChecker): patterns = ('*.py',) results_regex = re.compile('stdin:([0-9]+):([0-9]+)(.*)') def __init__(self, content): super().__init__() self.__content = content def check(self, line_numbers): issues = [] data = ''.join(self.__content).encode('utf-8') try: ret = subprocess.run(['pycodestyle', '--ignore=E501', '-'], input=data, stdout=subprocess.PIPE) except FileNotFoundError: issues.append(StyleIssue(0, None, "Please install pycodestyle to validate python additions")) return issues results = ret.stdout.decode('utf-8').splitlines() for item in results: search = re.search(Pep8Checker.results_regex, item) line_number = int(search.group(1)) position = int(search.group(2)) msg = search.group(3) if line_number in line_numbers: line = self.__content[line_number - 1] issues.append(StyleIssue(line_number, line, msg)) return issues # ------------------------------------------------------------------------------ # Formatters # _formatters = [] class FormatterRegistry(type): def __new__(cls, clsname, bases, attrs): newclass = super(FormatterRegistry, cls).__new__(cls, clsname, bases, attrs) if clsname != 'Formatter': _formatters.append(newclass) return newclass class Formatter(metaclass=FormatterRegistry): enabled = True def __init__(self): pass # # Class methods # @classmethod def formatters(cls, filename): for formatter in _formatters: if not cls.enabled: continue if formatter.supports(filename): yield formatter @classmethod def supports(cls, filename): if not cls.enabled: return False for pattern in cls.patterns: if fnmatch.fnmatch(os.path.basename(filename), pattern): return True return False @classmethod def all_patterns(cls): patterns = set() for formatter in _formatters: if not cls.enabled: continue patterns.update(formatter.patterns) return patterns class AStyleFormatter(Formatter): enabled = False patterns = ('*.c', '*.cpp', '*.h') @classmethod def format(cls, filename, data): ret = subprocess.run(['astyle', *astyle_options], input=data.encode('utf-8'), stdout=subprocess.PIPE) return ret.stdout.decode('utf-8') class CLangFormatter(Formatter): enabled = False patterns = ('*.c', '*.cpp', '*.h') @classmethod def format(cls, filename, data): ret = subprocess.run(['clang-format', '-style=file', '-assume-filename=' + filename], input=data.encode('utf-8'), stdout=subprocess.PIPE) retur */ #include "encoder_libjpeg.h" #include <fcntl.h> #include <iomanip> #include <iostream> #include <sstream> #include <string.h> #include <sys/mman.h> #include <unistd.h> #include <vector> #include <libcamera/camera.h> #include <libcamera/formats.h> #include <libcamera/pixel_format.h> #include "libcamera/internal/formats.h" #include "libcamera/internal/log.h" using namespace libcamera; LOG_DECLARE_CATEGORY(JPEG) namespace { struct JPEGPixelFormatInfo { J_COLOR_SPACE colorSpace; const PixelFormatInfo &pixelFormatInfo; bool nvSwap; }; const std::map<PixelFormat, JPEGPixelFormatInfo> pixelInfo{ { formats::R8, { JCS_GRAYSCALE, PixelFormatInfo::info(formats::R8), false } }, { formats::RGB888, { JCS_EXT_BGR, PixelFormatInfo::info(formats::RGB888), false } }, { formats::BGR888, { JCS_EXT_RGB, PixelFormatInfo::info(formats::BGR888), false } }, { formats::NV12, { JCS_YCbCr, PixelFormatInfo::info(formats::NV12), false } }, { formats::NV21, { JCS_YCbCr, PixelFormatInfo::info(formats::NV21), true } }, { formats::NV16, { JCS_YCbCr, PixelFormatInfo::info(formats::NV16), false } }, { formats::NV61, { JCS_YCbCr, PixelFormatInfo::info(formats::NV61), true } }, { formats::NV24, { JCS_YCbCr, PixelFormatInfo::info(formats::NV24), false } }, { formats::NV42, { JCS_YCbCr, PixelFormatInfo::info(formats::NV42), true } }, }; const struct JPEGPixelFormatInfo &findPixelInfo(const PixelFormat &format) { static const struct JPEGPixelFormatInfo invalidPixelFormat { JCS_UNKNOWN, PixelFormatInfo(), false }; const auto iter = pixelInfo.find(format); if (iter == pixelInfo.end()) { LOG(JPEG, Error) << "Unsupported pixel format for JPEG encoder: " << format.toString(); return invalidPixelFormat; } return iter->second; } } /* namespace */ EncoderLibJpeg::EncoderLibJpeg() : quality_(95) { /* \todo Expand error handling coverage with a custom handler. */ compress_.err = jpeg_std_error(&jerr_); jpeg_create_compress(&compress_); } EncoderLibJpeg::~EncoderLibJpeg() { jpeg_destroy_compress(&compress_); } int EncoderLibJpeg::configure(const StreamConfiguration &cfg) { const struct JPEGPixelFormatInfo info = findPixelInfo(cfg.pixelFormat); if (info.colorSpace == JCS_UNKNOWN) return -ENOTSUP; compress_.image_width = cfg.size.width; compress_.image_height = cfg.size.height; compress_.in_color_space = info.colorSpace; compress_.input_components = info.colorSpace == JCS_GRAYSCALE ? 1 : 3; jpeg_set_defaults(&compress_); jpeg_set_quality(&compress_, quality_, TRUE); pixelFormatInfo_ = &info.pixelFormatInfo; nv_ = pixelFormatInfo_->numPlanes() == 2; nvSwap_ = info.nvSwap; return 0; } void EncoderLibJpeg::compressRGB(const MappedBuffer *frame) { unsigned char *src = static_cast<unsigned char *>(frame->maps()[0].data()); /* \todo Stride information should come from buffer configuration. */ unsigned int stride = pixelFormatInfo_->stride(compress_.image_width, 0); JSAMPROW row_pointer[1]; while (compress_.next_scanline < compress_.image_height) { row_pointer[0] = &src[compress_.next_scanline * stride]; jpeg_write_scanlines(&compress_, row_pointer, 1); } } /* * Compress the incoming buffer from a supported NV format. * This naively unpacks the semi-planar NV12 to a YUV888 format for libjpeg. */ void EncoderLibJpeg::compressNV(const MappedBuffer *frame) { uint8_t tmprowbuf[compress_.image_width * 3]; /* * \todo Use the raw api, and only unpack the cb/cr samples to new line * buffers. If possible, see if we can set appropriate pixel strides * too to save even that copy. * * Possible hints at: * https://sourceforge.net/p/libjpeg/mailman/message/30815123/ */ unsigned int y_stride = pixelFormatInfo_->stride(compress_.image_width, 0); unsigned int c_stride = pixelFormatInfo_->stride(compress_.image_width, 1); unsigned int horzSubSample = 2 * compress_.image_width / c_stride; unsigned int vertSubSample = pixelFormatInfo_->planes[1].verticalSubSampling; unsigned int c_inc = horzSubSample == 1 ? 2 : 0; unsigned int cb_pos = nvSwap_ ? 1 : 0; unsigned int cr_pos = nvSwap_ ? 0 : 1; const unsigned char *src = static_cast<unsigned char *>(frame->maps()[0].data()); const unsigned char *src_c = src + y_stride * compress_.image_height; JSAMPROW row_pointer[1]; row_pointer[0] = &tmprowbuf[0]; for (unsigned int y = 0; y < compress_.image_height; y++) { unsigned char *dst = &tmprowbuf[0]; const unsigned char *src_y = src + y * compress_.image_width; const unsigned char *src_cb = src_c + (y / vertSubSample) * c_stride + cb_pos; const unsigned char *src_cr = src_c + (y / vertSubSample) * c_stride + cr_pos; for (unsigned int x = 0; x < compress_.image_width; x += 2) { dst[0] = *src_y; dst[1] = *src_cb; dst[2] = *src_cr; src_y++; src_cb += c_inc; src_cr += c_inc; dst += 3; dst[0] = *src_y; dst[1] = *src_cb; dst[2] = *src_cr; src_y++; src_cb += 2; src_cr += 2; dst += 3; } jpeg_write_scanlines(&compress_, row_pointer, 1); } } int EncoderLibJpeg::encode(const FrameBuffer &source, Span<uint8_t> dest, const Span<const uint8_t> &exifData) { MappedFrameBuffer frame(&source, PROT_READ); if (!frame.isValid()) { LOG(JPEG, Error) << "Failed to map FrameBuffer : " << strerror(frame.error()); return frame.error(); } unsigned char *destination = dest.data(); unsigned long size = dest.size(); /* * The jpeg_mem_dest will reallocate if the required size is not * sufficient. That means the output won't be written to the correct * buffers. * * \todo Implement our own custom memory destination to prevent * reallocation and prefer failure with correct reporting. */ jpeg_mem_dest(&compress_, &destination, &size); jpeg_start_compress(&compress_, TRUE); if (exifData.size()) /* Store Exif data in the JPEG_APP1 data block. */ jpeg_write_marker(&compress_, JPEG_APP0 + 1, static_cast<const JOCTET *>(exifData.data()), exifData.size()); LOG(JPEG, Debug) << "JPEG Encode Starting:" << compress_.image_width << "x" << compress_.image_height; if (nv_) compressNV(&frame); else compressRGB(&frame); jpeg_finish_compress(&compress_); return size; }