Giả sử bạn đang làm việc với màu RGB: mỗi màu được biểu thị bằng ba cường độ hoặc độ sáng. Bạn phải chọn giữa "RGB tuyến tính" và "sRGB". Hiện tại, chúng tôi sẽ đơn giản hóa mọi thứ bằng cách bỏ qua ba cường độ khác nhau và giả sử bạn chỉ có một cường độ: nghĩa là, bạn chỉ xử lý các sắc thái của màu xám.
Trong không gian màu tuyến tính, mối quan hệ giữa các số bạn lưu trữ và cường độ mà chúng đại diện là tuyến tính. Thực tế, điều này có nghĩa là nếu bạn tăng gấp đôi con số, bạn sẽ tăng gấp đôi cường độ (độ đậm nhạt của màu xám). Nếu bạn muốn thêm hai cường độ lại với nhau (vì bạn đang tính toán cường độ dựa trên sự đóng góp của hai nguồn sáng hoặc vì bạn đang thêm một vật thể trong suốt lên trên một vật thể mờ đục), bạn có thể thực hiện việc này bằng cách thêm hai số với nhau. Nếu bạn đang thực hiện bất kỳ loại pha trộn 2D hoặc đổ bóng 3D nào hoặc hầu như bất kỳ xử lý hình ảnh nào, thì bạn muốn các cường độ của mình trong không gian màu tuyến tính, vì vậy bạn có thể chỉ cần cộng, trừ, nhân và chia các số để có cùng tác động đến các cường độ. Hầu hết các thuật toán xử lý và kết xuất màu chỉ cho kết quả chính xác với RGB tuyến tính, trừ khi bạn thêm trọng số bổ sung cho mọi thứ.
Điều đó nghe thực sự dễ dàng, nhưng có một vấn đề. Độ nhạy của mắt người đối với ánh sáng ở cường độ thấp tốt hơn ở cường độ cao. Có nghĩa là, nếu bạn lập danh sách tất cả các cường độ mà bạn có thể phân biệt, sẽ có nhiều cường độ tối hơn cường độ sáng. Nói một cách khác, bạn có thể phân biệt các sắc độ xám đậm hơn so với các sắc độ xám nhạt. Đặc biệt, nếu bạn đang sử dụng 8 bit để thể hiện cường độ của mình và bạn làm điều này trong không gian màu tuyến tính, bạn sẽ có quá nhiều sắc thái sáng và không đủ sắc thái tối. Bạn nhận được dải trong các vùng tối của mình, trong khi ở các vùng sáng, bạn đang lãng phí các sắc thái gần trắng khác nhau mà người dùng không thể phân biệt được.
Để tránh vấn đề này và sử dụng tốt nhất 8 bit đó, chúng tôi có xu hướng sử dụng sRGB . Tiêu chuẩn sRGB cho bạn biết một đường cong để sử dụng, để làm cho màu sắc của bạn trở nên phi tuyến tính. Đường cong nông hơn ở phía dưới, vì vậy bạn có thể có nhiều màu xám đậm hơn và dốc hơn ở phía trên, do đó bạn có ít màu xám nhạt hơn. Nếu bạn tăng gấp đôi số lượng, bạn sẽ tăng gấp đôi cường độ. Điều này có nghĩa là nếu bạn thêm các màu sRGB với nhau, bạn sẽ có kết quả nhạt hơn so với mức cần thiết. Ngày nay, hầu hết các màn hình diễn giải màu đầu vào của chúng là sRGB. Vì vậy, khi bạn đặt màu trên màn hình hoặc lưu trữ nó trong kết cấu 8 bit mỗi kênh, hãy lưu trữ nó dưới dạng sRGB , để bạn sử dụng tốt nhất 8 bit đó.
Bạn sẽ nhận thấy hiện tại chúng tôi có một vấn đề: chúng tôi muốn màu sắc của chúng tôi được xử lý trong không gian tuyến tính, nhưng được lưu trữ trong sRGB. Điều này có nghĩa là bạn sẽ thực hiện chuyển đổi sRGB sang tuyến tính khi đọc và chuyển đổi tuyến tính thành sRGB khi ghi. Như chúng ta đã nói rằng cường độ 8 bit tuyến tính không có đủ tối, điều này sẽ gây ra vấn đề, vì vậy có một quy tắc thực tế hơn: không sử dụng màu tuyến tính 8 bit nếu bạn có thể tránh nó. Việc tuân theo quy tắc màu 8-bit luôn là sRGB đã trở nên thông thường, vì vậy bạn thực hiện chuyển đổi từ sRGB sang tuyến tính cùng lúc với việc mở rộng cường độ từ 8 lên 16 bit hoặc từ số nguyên sang dấu phẩy động; tương tự như vậy, khi bạn hoàn thành quá trình xử lý dấu phẩy động, bạn thu hẹp còn 8 bit cùng lúc khi chuyển đổi sang sRGB. Nếu bạn tuân theo các quy tắc này,
Khi bạn đang đọc một hình ảnh sRGB và bạn muốn có cường độ tuyến tính, hãy áp dụng công thức này cho từng cường độ:
float s = read_channel();
float linear;
if (s <= 0.04045) linear = s / 12.92;
else linear = pow((s + 0.055) / 1.055, 2.4);
Theo cách khác, khi bạn muốn viết một hình ảnh dưới dạng sRGB, hãy áp dụng công thức này cho từng cường độ tuyến tính:
float linear = do_processing();
float s;
if (linear <= 0.0031308) s = linear * 12.92;
else s = 1.055 * pow(linear, 1.0/2.4) - 0.055; ( Edited: The previous version is -0.55 )
Trong cả hai trường hợp, giá trị dấu phẩy động s nằm trong khoảng từ 0 đến 1, vì vậy nếu bạn đang đọc số nguyên 8 bit, bạn muốn chia cho 255 trước và nếu bạn đang viết số nguyên 8 bit, bạn muốn nhân với 255 cuối cùng, giống như cách bạn thường làm. Đó là tất cả những gì bạn cần biết để làm việc với sRGB.
Cho đến nay, tôi chỉ xử lý một cường độ, nhưng có những thứ khôn ngoan hơn liên quan đến màu sắc. Mắt người có thể phân biệt các độ sáng khác nhau tốt hơn các sắc thái khác nhau (về mặt kỹ thuật, nó có độ phân giải độ sáng tốt hơn độ chói), vì vậy bạn có thể sử dụng tốt hơn 24 bit của mình bằng cách lưu trữ độ sáng riêng biệt với sắc thái màu. Đây là những gì các đại diện YUV, YCrCb, v.v. cố gắng làm. Kênh Y là độ đậm nhạt tổng thể của màu và sử dụng nhiều bit hơn (hoặc có độ phân giải không gian cao hơn) so với hai kênh còn lại. Bằng cách này, bạn không (luôn luôn) cần phải áp dụng một đường cong giống như bạn làm với cường độ RGB. YUV là một không gian màu tuyến tính, vì vậy nếu bạn nhân đôi số trong kênh Y, bạn sẽ tăng gấp đôi độ đậm nhạt của màu, nhưng bạn không thể thêm hoặc nhân các màu YUV với nhau như bạn có thể làm với màu RGB, vì vậy '
Tôi nghĩ rằng điều đó trả lời câu hỏi của bạn, vì vậy tôi sẽ kết thúc bằng một ghi chú lịch sử nhanh chóng. Trước sRGB, các CRT cũ đã từng được tích hợp sẵn tính năng phi tuyến tính. Nếu bạn tăng gấp đôi điện áp cho một pixel, bạn sẽ tăng gấp đôi cường độ. Mỗi màn hình khác nhau thêm bao nhiêu và thông số này được gọi là gamma . Hành vi này hữu ích vì nó có nghĩa là bạn có thể nhận được nhiều bóng tối hơn ánh sáng, nhưng cũng có nghĩa là bạn không thể biết màu sắc của mình sẽ sáng như thế nào trên CRT của người dùng, trừ khi bạn đã hiệu chỉnh nó trước. Hiệu chỉnh gammanghĩa là chuyển đổi các màu bạn bắt đầu (có thể là tuyến tính) và biến đổi chúng cho gamma của CRT của người dùng. OpenGL xuất phát từ thời đại này, đó là lý do tại sao hành vi sRGB của nó đôi khi hơi khó hiểu. Nhưng các nhà cung cấp GPU hiện có xu hướng làm việc với quy ước mà tôi đã mô tả ở trên: đó là khi bạn lưu trữ cường độ 8-bit trong kết cấu hoặc bộ đệm khung, đó là sRGB và khi bạn xử lý màu, đó là tuyến tính. Ví dụ: OpenGL ES 3.0, mỗi bộ đệm khung và kết cấu đều có "cờ sRGB" mà bạn có thể bật để bật chuyển đổi tự động khi đọc và ghi. Bạn không cần phải thực hiện chuyển đổi sRGB hoặc hiệu chỉnh gamma một cách rõ ràng.